├── server ├── .gitignore ├── __pycache__ │ ├── main.cpython-310.pyc │ └── main.cpython-311.pyc ├── requirements.txt ├── .dockerignore ├── README.Docker.md ├── compose.yaml ├── Dockerfile └── main.py ├── client ├── src │ ├── context │ │ ├── AppContext.jsx │ │ ├── PermissionsContext.jsx │ │ ├── FormContext.jsx │ │ └── ImageContext.jsx │ ├── constants │ │ ├── color.js │ │ └── svg.js │ ├── assets │ │ └── svg │ │ │ ├── camera-icon.svg │ │ │ ├── gallery-icon.svg │ │ │ ├── allowgallery.svg │ │ │ └── allowcamera.svg │ ├── components │ │ ├── input │ │ │ ├── Slider.jsx │ │ │ ├── Input.jsx │ │ │ └── Checkbox.jsx │ │ ├── ProgressStepBar.jsx │ │ ├── Footer.jsx │ │ ├── button │ │ │ ├── SecondaryButton.jsx │ │ │ └── PrimaryButton.jsx │ │ └── common │ │ │ └── BaseScreen.jsx │ ├── helpers │ │ └── scale.js │ ├── navigation │ │ └── MainNavigator.jsx │ ├── screens │ │ ├── DiagnosisScreen.jsx │ │ ├── Welcome.jsx │ │ ├── GrantGalleryPermissionScreen.jsx │ │ ├── GrantCameraPermissionScreen.jsx │ │ ├── FormScreen.jsx │ │ └── ScanPhotoScreen.jsx │ └── hooks │ │ └── useGlobalStyle.js ├── assets │ ├── icon.png │ ├── splash.png │ ├── favicon.png │ └── adaptive-icon.png ├── .prettierrc ├── eas.json ├── .gitignore ├── js.config.json ├── babel.config.js ├── metro.config.js ├── App.js ├── app.json └── package.json ├── model ├── .gitignore ├── __pycache__ │ ├── handcrafted.cpython-311.pyc │ ├── modelcrafted.cpython-311.pyc │ └── imgCropBinaryMask.cpython-311.pyc ├── .dockerignore ├── README.Docker.md ├── requirements.txt ├── compose.yaml ├── smote.py ├── Dockerfile ├── modelcrafted.py ├── fusion_stage.py ├── python-compile-scripts │ └── pycompiler-args.py ├── notebooks │ ├── test_dataset.ipynb │ └── handcrafted.ipynb ├── img-crop-binary-mask.py ├── handcrafted.py └── imgCropBinaryMask.py ├── README.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md └── LICENSE /server/.gitignore: -------------------------------------------------------------------------------- 1 | *pyc -------------------------------------------------------------------------------- /client/src/context/AppContext.jsx: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /model/.gitignore: -------------------------------------------------------------------------------- 1 | __pyache__ 2 | notebooks -------------------------------------------------------------------------------- /client/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VukIG/Melanoma-Detector/HEAD/client/assets/icon.png -------------------------------------------------------------------------------- /client/assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VukIG/Melanoma-Detector/HEAD/client/assets/splash.png -------------------------------------------------------------------------------- /client/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "jsxBracketSameLine": false, 3 | "semi": true, 4 | "singleQuote": true 5 | } 6 | -------------------------------------------------------------------------------- /client/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VukIG/Melanoma-Detector/HEAD/client/assets/favicon.png -------------------------------------------------------------------------------- /client/assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VukIG/Melanoma-Detector/HEAD/client/assets/adaptive-icon.png -------------------------------------------------------------------------------- /server/__pycache__/main.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VukIG/Melanoma-Detector/HEAD/server/__pycache__/main.cpython-310.pyc -------------------------------------------------------------------------------- /server/__pycache__/main.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VukIG/Melanoma-Detector/HEAD/server/__pycache__/main.cpython-311.pyc -------------------------------------------------------------------------------- /model/__pycache__/handcrafted.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VukIG/Melanoma-Detector/HEAD/model/__pycache__/handcrafted.cpython-311.pyc -------------------------------------------------------------------------------- /model/__pycache__/modelcrafted.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VukIG/Melanoma-Detector/HEAD/model/__pycache__/modelcrafted.cpython-311.pyc -------------------------------------------------------------------------------- /server/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi==0.109.2 2 | numpy==1.26.4 3 | Pillow==10.2.0 4 | pydantic==2.6.1 5 | uvicorn==0.25.0 6 | requests==2.31.0 7 | python-multipart -------------------------------------------------------------------------------- /model/__pycache__/imgCropBinaryMask.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VukIG/Melanoma-Detector/HEAD/model/__pycache__/imgCropBinaryMask.cpython-311.pyc -------------------------------------------------------------------------------- /client/src/constants/color.js: -------------------------------------------------------------------------------- 1 | export const colors = { 2 | primary: '#5caafe', 3 | secondary: '#46c3ff', 4 | white: '#F5F5F5', 5 | black: '#000000', 6 | gray: '#AFB4B8', 7 | }; 8 | -------------------------------------------------------------------------------- /client/eas.json: -------------------------------------------------------------------------------- 1 | { 2 | "cli": { 3 | "version": ">= 7.5.0" 4 | }, 5 | "build": { 6 | "development": { 7 | "developmentClient": true, 8 | "distribution": "internal" 9 | }, 10 | "preview": { 11 | "distribution": "internal" 12 | }, 13 | "production": {} 14 | }, 15 | "submit": { 16 | "production": {} 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /client/src/constants/svg.js: -------------------------------------------------------------------------------- 1 | import WelcomeSvg from '@assets/svg/welcome.svg'; 2 | import AllowGallery from '@assets/svg/allowgallery.svg'; 3 | import AllowCamera from '@assets/svg/allowcamera.svg'; 4 | import CameraIcon from '@assets/svg/camera-icon.svg'; 5 | import GalleryIcon from '@assets/svg/gallery-icon.svg'; 6 | 7 | export { WelcomeSvg, AllowCamera, AllowGallery, CameraIcon, GalleryIcon }; 8 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files 2 | 3 | # dependencies 4 | node_modules/ 5 | 6 | # Expo 7 | .expo/ 8 | dist/ 9 | web-build/ 10 | 11 | # Native 12 | *.orig.* 13 | *.jks 14 | *.p8 15 | *.p12 16 | *.key 17 | *.mobileprovision 18 | 19 | # Metro 20 | .metro-health-check* 21 | 22 | # debug 23 | npm-debug.* 24 | yarn-debug.* 25 | yarn-error.* 26 | 27 | # macOS 28 | .DS_Store 29 | *.pem 30 | 31 | # local env files 32 | .env*.local 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | -------------------------------------------------------------------------------- /client/js.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "expo/jsconfig.base", 3 | "compilerOptions": { 4 | "strict": true, 5 | "baseUrl": ".", 6 | "paths": { 7 | "@assets/*": ["src/assets/*"], 8 | "@components/*": ["src/components/*"], 9 | "@constants/*": ["src/constants/*"], 10 | "@hooks/*": ["src/hooks/*"], 11 | "@helpers/*": ["src/helpers/*"], 12 | "@screens/*": ["src/screens/*"], 13 | "@navigation/*": ["src/navigation/*"] 14 | } 15 | }, 16 | "exclude": ["node_modules", "build", "dist"] 17 | } 18 | -------------------------------------------------------------------------------- /client/src/assets/svg/camera-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /client/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | plugins: [ 6 | [ 7 | 'module-resolver', 8 | { 9 | root: ['.'], 10 | alias: { 11 | '@assets': './src/assets', 12 | '@components': './src/components', 13 | '@constants': './src/constants', 14 | '@hooks': './src/hooks', 15 | '@helpers': './src/helpers', 16 | '@screens': './src/screens', 17 | '@navigation': './src/navigation', 18 | }, 19 | }, 20 | ], 21 | ], 22 | }; 23 | }; 24 | -------------------------------------------------------------------------------- /client/metro.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | const path = require('path'); 3 | 4 | const { getDefaultConfig } = require('expo/metro-config'); 5 | 6 | module.exports = (() => { 7 | const config = getDefaultConfig(__dirname); 8 | 9 | const { transformer, resolver } = config; 10 | 11 | config.transformer = { 12 | ...transformer, 13 | babelTransformerPath: require.resolve('react-native-svg-transformer'), 14 | }; 15 | config.resolver = { 16 | ...resolver, 17 | assetExts: resolver.assetExts.filter((ext) => ext !== 'svg'), 18 | sourceExts: [...resolver.sourceExts, 'svg'], 19 | extraNodeModules: { 20 | src: path.resolve(__dirname, 'src'), 21 | }, 22 | }; 23 | 24 | return config; 25 | })(); 26 | -------------------------------------------------------------------------------- /client/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import 'react-native-gesture-handler'; 3 | import { SafeAreaProvider } from 'react-native-safe-area-context'; 4 | import MainNavigator from './src/navigation/MainNavigator'; 5 | import { PermissionProvider } from './src/context/PermissionsContext'; 6 | import { ImageProvider } from './src/context/ImageContext'; 7 | import { FormProvider } from './src/context/FormContext'; 8 | 9 | export default function App() { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /model/.dockerignore: -------------------------------------------------------------------------------- 1 | # Include any files or directories that you don't want to be copied to your 2 | # container here (e.g., local build artifacts, temporary files, etc.). 3 | # 4 | # For more help, visit the .dockerignore file reference guide at 5 | # https://docs.docker.com/go/build-context-dockerignore/ 6 | 7 | **/.DS_Store 8 | **/__pycache__ 9 | **/.venv 10 | **/.classpath 11 | **/.dockerignore 12 | **/.env 13 | **/.git 14 | **/.gitignore 15 | **/.project 16 | **/.settings 17 | **/.toolstarget 18 | **/.vs 19 | **/.vscode 20 | **/*.*proj.user 21 | **/*.dbmdl 22 | **/*.jfm 23 | **/bin 24 | **/charts 25 | **/docker-compose* 26 | **/compose* 27 | **/Dockerfile* 28 | **/node_modules 29 | **/npm-debug.log 30 | **/obj 31 | **/secrets.dev.yaml 32 | **/values.dev.yaml 33 | LICENSE 34 | README.md 35 | -------------------------------------------------------------------------------- /server/.dockerignore: -------------------------------------------------------------------------------- 1 | # Include any files or directories that you don't want to be copied to your 2 | # container here (e.g., local build artifacts, temporary files, etc.). 3 | # 4 | # For more help, visit the .dockerignore file reference guide at 5 | # https://docs.docker.com/go/build-context-dockerignore/ 6 | 7 | **/.DS_Store 8 | **/__pycache__ 9 | **/.venv 10 | **/.classpath 11 | **/.dockerignore 12 | **/.env 13 | **/.git 14 | **/.gitignore 15 | **/.project 16 | **/.settings 17 | **/.toolstarget 18 | **/.vs 19 | **/.vscode 20 | **/*.*proj.user 21 | **/*.dbmdl 22 | **/*.jfm 23 | **/bin 24 | **/charts 25 | **/docker-compose* 26 | **/compose* 27 | **/Dockerfile* 28 | **/node_modules 29 | **/npm-debug.log 30 | **/obj 31 | **/secrets.dev.yaml 32 | **/values.dev.yaml 33 | LICENSE 34 | README.md 35 | -------------------------------------------------------------------------------- /client/src/components/input/Slider.jsx: -------------------------------------------------------------------------------- 1 | import { View } from 'react-native'; 2 | import DropDownPicker from 'react-native-dropdown-picker'; 3 | import { useGlobalStyle } from '../../hooks/useGlobalStyle'; 4 | import { useState } from 'react'; 5 | 6 | const Slider = ({ items, setItems, locVal, setLocVal }) => { 7 | const [open, setOpen] = useState(false); 8 | 9 | const basicStyles = useGlobalStyle(); 10 | return ( 11 | 12 | 22 | 23 | ); 24 | }; 25 | 26 | export default Slider; 27 | -------------------------------------------------------------------------------- /model/README.Docker.md: -------------------------------------------------------------------------------- 1 | ### Building and running your application 2 | 3 | When you're ready, start your application by running: 4 | `docker compose up --build`. 5 | 6 | Your application will be available at http://localhost:8000. 7 | 8 | ### Deploying your application to the cloud 9 | 10 | First, build your image, e.g.: `docker build -t myapp .`. 11 | If your cloud uses a different CPU architecture than your development 12 | machine (e.g., you are on a Mac M1 and your cloud provider is amd64), 13 | you'll want to build the image for that platform, e.g.: 14 | `docker build --platform=linux/amd64 -t myapp .`. 15 | 16 | Then, push it to your registry, e.g. `docker push myregistry.com/myapp`. 17 | 18 | Consult Docker's [getting started](https://docs.docker.com/go/get-started-sharing/) 19 | docs for more detail on building and pushing. 20 | 21 | ### References 22 | * [Docker's Python guide](https://docs.docker.com/language/python/) -------------------------------------------------------------------------------- /server/README.Docker.md: -------------------------------------------------------------------------------- 1 | ### Building and running your application 2 | 3 | When you're ready, start your application by running: 4 | `docker compose up --build`. 5 | 6 | Your application will be available at http://localhost:7999. 7 | 8 | ### Deploying your application to the cloud 9 | 10 | First, build your image, e.g.: `docker build -t myapp .`. 11 | If your cloud uses a different CPU architecture than your development 12 | machine (e.g., you are on a Mac M1 and your cloud provider is amd64), 13 | you'll want to build the image for that platform, e.g.: 14 | `docker build --platform=linux/amd64 -t myapp .`. 15 | 16 | Then, push it to your registry, e.g. `docker push myregistry.com/myapp`. 17 | 18 | Consult Docker's [getting started](https://docs.docker.com/go/get-started-sharing/) 19 | docs for more detail on building and pushing. 20 | 21 | ### References 22 | * [Docker's Python guide](https://docs.docker.com/language/python/) -------------------------------------------------------------------------------- /client/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "DermAI", 4 | "slug": "DermAI", 5 | "version": "1.0.1", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "userInterfaceStyle": "light", 9 | "splash": { 10 | "image": "./assets/splash.png", 11 | "resizeMode": "contain", 12 | "backgroundColor": "#ffffff" 13 | }, 14 | "assetBundlePatterns": [ 15 | "**/*" 16 | ], 17 | "ios": { 18 | "supportsTablet": true 19 | }, 20 | "android": { 21 | "package": "com.vukig.DermAI", 22 | "versionCode": 2, 23 | "adaptiveIcon": { 24 | "foregroundImage": "./assets/adaptive-icon.png", 25 | "backgroundColor": "#ffffff" 26 | } 27 | }, 28 | "web": { 29 | "favicon": "./assets/favicon.png" 30 | }, 31 | "plugins": [ 32 | "expo-font" 33 | ], 34 | "extra": { 35 | "eas": { 36 | "projectId": "77959661-4551-4b86-865e-4d15027f67f1" 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /client/src/components/ProgressStepBar.jsx: -------------------------------------------------------------------------------- 1 | import { View } from 'react-native'; 2 | import PropTypes from 'prop-types'; 3 | import { scale } from '../helpers/scale'; 4 | import { useGlobalStyle } from '../hooks/useGlobalStyle'; 5 | import { colors } from '../constants/color'; 6 | 7 | export const ProgressStepBar = ({ stepSize = 5, currentStepIndex = 0 }) => { 8 | const basicStyles = useGlobalStyle(); 9 | 10 | const StepCircle = ({ isPending = false }) => ( 11 | 19 | ); 20 | return ( 21 | 22 | {Array.from({ length: stepSize }).map((item, index) => ( 23 | 27 | ))} 28 | 29 | ); 30 | }; 31 | 32 | ProgressStepBar.propTypes = { 33 | stepSize: PropTypes.number, 34 | currentStepIndex: PropTypes.number, 35 | }; 36 | -------------------------------------------------------------------------------- /client/src/helpers/scale.js: -------------------------------------------------------------------------------- 1 | import { Dimensions } from 'react-native'; 2 | 3 | // Guideline sizes are based on standard screen mobile device 4 | const guidelineBaseWidth = 428; 5 | const guidelineBaseHeight = 926; 6 | 7 | const realWidth = Dimensions.get('screen').width; 8 | const realHeight = Dimensions.get('screen').height; 9 | 10 | const innerScale = 11 | Dimensions.get('screen').scale / Dimensions.get('window').scale; 12 | const baseRatio = guidelineBaseWidth / guidelineBaseHeight; 13 | const realRatio = realWidth / realHeight; 14 | 15 | const width = Dimensions.get('window').width * innerScale; 16 | const height = 17 | (Dimensions.get('window').height * innerScale) / 18 | (realRatio > baseRatio ? realRatio / baseRatio : 1); 19 | 20 | const scale = (size) => (width / guidelineBaseWidth) * size; 21 | const scaleVertical = (size) => (height / guidelineBaseHeight) * size; 22 | const scaleModerate = (size, factor = 0.5) => 23 | size + (scale(size) - size) * factor; 24 | const scaleImage = (width, height, targetHeight) => ({ 25 | width: (targetHeight / height) * width, 26 | height: targetHeight, 27 | }); 28 | 29 | export { 30 | baseRatio, 31 | width, 32 | height, 33 | scale, 34 | scaleVertical, 35 | scaleModerate, 36 | scaleImage, 37 | }; 38 | -------------------------------------------------------------------------------- /client/src/components/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Text, View, TouchableOpacity, StyleSheet } from 'react-native'; 3 | import { AntDesign } from '@expo/vector-icons'; 4 | import { Linking } from 'react-native'; 5 | 6 | function Footer() { 7 | return ( 8 | 9 | 10 | Made by the MDA team 11 | 13 | Linking.openURL( 14 | 'https://www.linkedin.com/in/vuk-ignjatovic-53152a248/', 15 | ) 16 | } 17 | > 18 | 19 | 20 | 21 | 22 | ); 23 | } 24 | 25 | const styles = StyleSheet.create({ 26 | footerContainer: { 27 | backgroundColor: '#4a90e2', 28 | padding: 10, 29 | position: 'absolute', 30 | bottom: 0, 31 | width: '120%', 32 | }, 33 | flexContainer: { 34 | flexDirection: 'row', 35 | justifyContent: 'center', 36 | alignItems: 'center', 37 | }, 38 | textStyle: { 39 | color: 'white', 40 | marginRight: 10, 41 | }, 42 | }); 43 | 44 | export default Footer; 45 | -------------------------------------------------------------------------------- /client/src/components/input/Input.jsx: -------------------------------------------------------------------------------- 1 | import { View, Text, TouchableOpacity, TextInput } from 'react-native'; 2 | import { useGlobalStyle } from '../../hooks/useGlobalStyle'; 3 | import { scaleVertical } from '../../helpers/scale'; 4 | import { colors } from '../../constants/color'; 5 | const Input = ({ placeholder, type, setState, value }) => { 6 | const basicStyles = useGlobalStyle(); 7 | 8 | const handleTextChange = (text) => { 9 | const numericValue = text.replace(/[^0-9]/g, ''); 10 | setState(numericValue); 11 | }; 12 | 13 | return ( 14 | 24 | 25 | 42 | 43 | ); 44 | }; 45 | 46 | export default Input; 47 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "1.0.0", 4 | "main": "node_modules/expo/AppEntry.js", 5 | "scripts": { 6 | "prettier": "prettier 'src/**/*.js'", 7 | "_comment": "This is optional, tunneling is not needed when you are on pc -Vuk Stefanovic Karadzic", 8 | "start": "npx expo start --tunnel", 9 | "android": "expo start --android", 10 | "ios": "expo start --ios", 11 | "web": "expo start --web" 12 | }, 13 | "dependencies": { 14 | "@expo-google-fonts/montserrat": "^0.2.3", 15 | "@react-native-community/checkbox": "^0.5.17", 16 | "@react-navigation/native": "^6.1.9", 17 | "@react-navigation/stack": "^6.3.20", 18 | "axios": "^1.6.7", 19 | "expo": "~50.0.7", 20 | "expo-font": "~11.10.3", 21 | "expo-image": "~1.10.6", 22 | "expo-image-picker": "^14.7.1", 23 | "expo-linear-gradient": "~12.7.2", 24 | "expo-status-bar": "~1.11.1", 25 | "react": "18.2.0", 26 | "react-native": "0.73.4", 27 | "react-native-bouncy-checkbox": "^3.0.7", 28 | "react-native-dropdown-picker": "^5.4.6", 29 | "react-native-gesture-handler": "~2.14.0", 30 | "react-native-safe-area-context": "4.8.2", 31 | "react-native-svg": "14.1.0", 32 | "react-native-webview": "^13.8.1" 33 | }, 34 | "devDependencies": { 35 | "@babel/core": "^7.20.0", 36 | "babel-plugin-module-resolver": "^5.0.0", 37 | "prettier": "^3.2.5", 38 | "prop-types": "^15.8.1", 39 | "react-native-svg-transformer": "^1.3.0" 40 | }, 41 | "private": true 42 | } 43 | -------------------------------------------------------------------------------- /client/src/assets/svg/gallery-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /model/requirements.txt: -------------------------------------------------------------------------------- 1 | absl-py==2.1.0 2 | astunparse==1.6.3 3 | certifi==2024.2.2 4 | charset-normalizer==3.3.2 5 | flatbuffers==24.3.25 6 | gast==0.5.4 7 | google-pasta==0.2.0 8 | grpcio==1.62.1 9 | h5py==3.10.0 10 | idna==3.6 11 | joblib==1.3.2 12 | keras==3.1.1 13 | libclang==18.1.1 14 | Markdown==3.6 15 | markdown-it-py==3.0.0 16 | MarkupSafe==2.1.5 17 | mdurl==0.1.2 18 | ml-dtypes==0.3.2 19 | namex==0.0.7 20 | numpy==1.26.4 21 | nvidia-cublas-cu12==12.3.4.1 22 | nvidia-cuda-cupti-cu12==12.3.101 23 | nvidia-cuda-nvcc-cu12==12.3.107 24 | nvidia-cuda-nvrtc-cu12==12.3.107 25 | nvidia-cuda-runtime-cu12==12.3.101 26 | nvidia-cudnn-cu12==8.9.7.29 27 | nvidia-cufft-cu12==11.0.12.1 28 | nvidia-curand-cu12==10.3.4.107 29 | nvidia-cusolver-cu12==11.5.4.101 30 | nvidia-cusparse-cu12==12.2.0.103 31 | nvidia-nccl-cu12==2.19.3 32 | nvidia-nvjitlink-cu12==12.3.101 33 | opt-einsum==3.3.0 34 | optree==0.11.0 35 | packaging==24.0 36 | pandas==2.2.1 37 | protobuf==4.25.3 38 | Pygments==2.17.2 39 | python-dateutil==2.9.0.post0 40 | pytz==2024.1 41 | requests==2.31.0 42 | rich==13.7.1 43 | scikit-learn==1.4.1.post1 44 | scikit-rvm @ https://github.com/JamesRitchie/scikit-rvm/archive/master.zip#sha256=ff645f89e04965397981ea644c66599f6e0f10d955008d82c19fe3f6ae51e166 45 | scipy==1.12.0 46 | six==1.16.0 47 | tensorboard==2.16.2 48 | tensorboard-data-server==0.7.2 49 | tensorflow==2.16.1 50 | tensorflow-io-gcs-filesystem==0.36.0 51 | termcolor==2.4.0 52 | threadpoolctl==3.4.0 53 | typing_extensions==4.10.0 54 | tzdata==2024.1 55 | urllib3==2.2.1 56 | Werkzeug==3.0.1 57 | wrapt==1.16.0 58 | -------------------------------------------------------------------------------- /client/src/components/button/SecondaryButton.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Text, TouchableOpacity, View } from 'react-native'; 3 | import { colors } from '../../constants/color'; 4 | import { useGlobalStyle } from '../../hooks/useGlobalStyle'; 5 | import { scale, scaleVertical } from '../../helpers/scale'; 6 | import PropTypes from 'prop-types'; 7 | 8 | export const SecondaryButton = ({ title, icon, style, onPress }) => { 9 | const basicStyles = useGlobalStyle(); 10 | 11 | return ( 12 | 27 | 30 | 37 | {title} 38 | 39 | {icon} 40 | 41 | 42 | ); 43 | }; 44 | 45 | SecondaryButton.propTypes = { 46 | title: PropTypes.string.isRequired, 47 | style: PropTypes.object, 48 | onPress: PropTypes.func, 49 | icon: PropTypes.element, 50 | }; 51 | -------------------------------------------------------------------------------- /client/src/context/PermissionsContext.jsx: -------------------------------------------------------------------------------- 1 | import { createContext, useState } from 'react'; 2 | import * as ImagePicker from 'expo-image-picker'; 3 | 4 | const PermissionsContext = createContext(); 5 | 6 | export const PermissionProvider = ({ children }) => { 7 | const [lastPressed, setLastPressed] = useState('none'); 8 | 9 | const [permissions, setPermissions] = useState({ 10 | camera: false, 11 | gallery: false, 12 | }); 13 | 14 | const grantPermission = async (isCamera) => { 15 | const keyValue = isCamera ? 'camera' : 'gallery'; 16 | let optionStatus; 17 | if (isCamera) { 18 | optionStatus = await ImagePicker.requestCameraPermissionsAsync(); 19 | } else { 20 | optionStatus = await ImagePicker.requestMediaLibraryPermissionsAsync(); 21 | } 22 | if (optionStatus.status == 'granted') { 23 | setPermissions((prev) => ({ 24 | ...prev, 25 | [keyValue]: true, 26 | })); 27 | console.log('CHIPICHIPI DABA DUBI DUBI DABA'); 28 | } else { 29 | setPermissions((prev) => ({ 30 | ...prev, 31 | [keyValue]: false, 32 | })); 33 | } 34 | setLastPressed((prev) => ({ 35 | ...prev, 36 | [keyValue]: true, 37 | })); 38 | }; 39 | 40 | return ( 41 | 50 | {children} 51 | 52 | ); 53 | }; 54 | 55 | export default PermissionsContext; 56 | -------------------------------------------------------------------------------- /client/src/navigation/MainNavigator.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createStackNavigator } from '@react-navigation/stack'; 3 | import { NavigationContainer } from '@react-navigation/native'; 4 | 5 | import WelcomeScreen from '@screens/Welcome'; 6 | import GrantCameraPermissionScreen from '@screens/GrantCameraPermissionScreen'; 7 | import GrantGalleryPermissionScreen from '@screens/GrantGalleryPermissionScreen'; 8 | import ScanPhotoScreen from '@screens/ScanPhotoScreen'; 9 | import DiagnosisScreen from '@screens/DiagnosisScreen'; 10 | import FormScreen from '@screens/FormScreen'; 11 | 12 | const MainStack = createStackNavigator(); 13 | 14 | const MainNavigator = () => { 15 | return ( 16 | 17 | 22 | 23 | 27 | 31 | 35 | 36 | 40 | 41 | 42 | ); 43 | }; 44 | export default MainNavigator; 45 | -------------------------------------------------------------------------------- /client/src/components/button/PrimaryButton.jsx: -------------------------------------------------------------------------------- 1 | import { TouchableOpacity, Text, View } from 'react-native'; 2 | import PropTypes from 'prop-types'; 3 | import { LinearGradient } from 'expo-linear-gradient'; 4 | import { colors } from '../../constants/color'; 5 | import { useGlobalStyle } from '../../hooks/useGlobalStyle'; 6 | import { scale, scaleVertical } from '../../helpers/scale'; 7 | 8 | export const PrimaryButton = ({ title, style, onPress, icon }) => { 9 | const basicStyles = useGlobalStyle(); 10 | 11 | return ( 12 | 13 | 29 | 30 | 31 | {title} 32 | 33 | {icon} 34 | 35 | 36 | 37 | ); 38 | }; 39 | 40 | PrimaryButton.propTypes = { 41 | title: PropTypes.string.isRequired, 42 | style: PropTypes.object, 43 | onPress: PropTypes.func, 44 | icon: PropTypes.element, 45 | }; 46 | 47 | PrimaryButton.propTypes = { 48 | title: PropTypes.string.isRequired, 49 | style: PropTypes.object, 50 | onPress: PropTypes.func, 51 | icon: PropTypes.element, 52 | }; 53 | -------------------------------------------------------------------------------- /model/compose.yaml: -------------------------------------------------------------------------------- 1 | # Comments are provided throughout this file to help you get started. 2 | # If you need more help, visit the Docker Compose reference guide at 3 | # https://docs.docker.com/go/compose-spec-reference/ 4 | 5 | # Here the instructions define your application as a service called "server". 6 | # This service is built from the Dockerfile in the current directory. 7 | # You can add other services your application may depend on here, such as a 8 | # database or a cache. For examples, see the Awesome Compose repository: 9 | # https://github.com/docker/awesome-compose 10 | services: 11 | server: 12 | build: 13 | context: . 14 | ports: 15 | - 8000:8000 16 | 17 | # The commented out section below is an example of how to define a PostgreSQL 18 | # database that your application can use. `depends_on` tells Docker Compose to 19 | # start the database before your application. The `db-data` volume persists the 20 | # database data between container restarts. The `db-password` secret is used 21 | # to set the database password. You must create `db/password.txt` and add 22 | # a password of your choosing to it before running `docker compose up`. 23 | # depends_on: 24 | # db: 25 | # condition: service_healthy 26 | # db: 27 | # image: postgres 28 | # restart: always 29 | # user: postgres 30 | # secrets: 31 | # - db-password 32 | # volumes: 33 | # - db-data:/var/lib/postgresql/data 34 | # environment: 35 | # - POSTGRES_DB=example 36 | # - POSTGRES_PASSWORD_FILE=/run/secrets/db-password 37 | # expose: 38 | # - 5432 39 | # healthcheck: 40 | # test: [ "CMD", "pg_isready" ] 41 | # interval: 10s 42 | # timeout: 5s 43 | # retries: 5 44 | # volumes: 45 | # db-data: 46 | # secrets: 47 | # db-password: 48 | # file: db/password.txt 49 | 50 | -------------------------------------------------------------------------------- /server/compose.yaml: -------------------------------------------------------------------------------- 1 | # Comments are provided throughout this file to help you get started. 2 | # If you need more help, visit the Docker Compose reference guide at 3 | # https://docs.docker.com/go/compose-spec-reference/ 4 | 5 | # Here the instructions define your application as a service called "server". 6 | # This service is built from the Dockerfile in the current directory. 7 | # You can add other services your application may depend on here, such as a 8 | # database or a cache. For examples, see the Awesome Compose repository: 9 | # https://github.com/docker/awesome-compose 10 | services: 11 | server: 12 | build: 13 | context: . 14 | ports: 15 | - 7999:7999 16 | 17 | # The commented out section below is an example of how to define a PostgreSQL 18 | # database that your application can use. `depends_on` tells Docker Compose to 19 | # start the database before your application. The `db-data` volume persists the 20 | # database data between container restarts. The `db-password` secret is used 21 | # to set the database password. You must create `db/password.txt` and add 22 | # a password of your choosing to it before running `docker compose up`. 23 | # depends_on: 24 | # db: 25 | # condition: service_healthy 26 | # db: 27 | # image: postgres 28 | # restart: always 29 | # user: postgres 30 | # secrets: 31 | # - db-password 32 | # volumes: 33 | # - db-data:/var/lib/postgresql/data 34 | # environment: 35 | # - POSTGRES_DB=example 36 | # - POSTGRES_PASSWORD_FILE=/run/secrets/db-password 37 | # expose: 38 | # - 5432 39 | # healthcheck: 40 | # test: [ "CMD", "pg_isready" ] 41 | # interval: 10s 42 | # timeout: 5s 43 | # retries: 5 44 | # volumes: 45 | # db-data: 46 | # secrets: 47 | # db-password: 48 | # file: db/password.txt 49 | 50 | -------------------------------------------------------------------------------- /server/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | # Comments are provided throughout this file to help you get started. 4 | # If you need more help, visit the Dockerfile reference guide at 5 | # https://docs.docker.com/go/dockerfile-reference/ 6 | 7 | # Want to help us make this template better? Share your feedback here: https://forms.gle/ybq9Krt8jtBL3iCk7 8 | 9 | ARG PYTHON_VERSION=3.11.5 10 | FROM python:${PYTHON_VERSION}-slim as base 11 | 12 | # Prevents Python from writing pyc files. 13 | ENV PYTHONDONTWRITEBYTECODE=1 14 | 15 | # Keeps Python from buffering stdout and stderr to avoid situations where 16 | # the application crashes without emitting any logs due to buffering. 17 | ENV PYTHONUNBUFFERED=1 18 | 19 | WORKDIR /app 20 | 21 | # Create a non-privileged user that the app will run under. 22 | # See https://docs.docker.com/go/dockerfile-user-best-practices/ 23 | ARG UID=10001 24 | RUN adduser \ 25 | --disabled-password \ 26 | --gecos "" \ 27 | --home "/nonexistent" \ 28 | --shell "/sbin/nologin" \ 29 | --no-create-home \ 30 | --uid "${UID}" \ 31 | appuser 32 | 33 | # Download dependencies as a separate step to take advantage of Docker's caching. 34 | # Leverage a cache mount to /root/.cache/pip to speed up subsequent builds. 35 | # Leverage a bind mount to requirements.txt to avoid having to copy them into 36 | # into this layer. 37 | RUN --mount=type=cache,target=/root/.cache/pip \ 38 | --mount=type=bind,source=requirements.txt,target=requirements.txt \ 39 | python -m pip install -r requirements.txt 40 | 41 | # Switch to the non-privileged user to run the application. 42 | USER appuser 43 | 44 | # Copy the source code into the container. 45 | COPY . . 46 | 47 | # Expose the port that the application listens on. 48 | EXPOSE 7999 49 | 50 | # Run the application. 51 | CMD uvicorn 'main:app' --host=0.0.0.0 --port=7999 52 | -------------------------------------------------------------------------------- /model/smote.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | from keras.preprocessing.image import ImageDataGenerator, array_to_img 4 | from imblearn.over_sampling import SMOTE 5 | 6 | patho = {'benign': 0, 'malignant': 1} 7 | 8 | imagegen = ImageDataGenerator() 9 | # Load train data from drive 10 | train_generator = imagegen.flow_from_directory("/home/vuk/Documents/ML_Data/HAM/processed/roi/", class_mode="binary", shuffle=False, batch_size=128, target_size=(224, 224), seed=42) 11 | x = np.concatenate([train_generator.next()[0] for i in range(train_generator.__len__())]) 12 | y = np.concatenate([train_generator.next()[1] for i in range(train_generator.__len__())]) 13 | 14 | # Convert color images to a vector 15 | X_train = x.reshape(1293, 224*224*3) 16 | 17 | # Apply SMOTE method to the minority class (malignant) 18 | sm = SMOTE(random_state=2) 19 | X_smote, y_smote = sm.fit_resample(X_train, y) 20 | 21 | Xsmote_img = X_smote.reshape(6700, 224, 224, 3) 22 | 23 | # This function returns the label name 24 | def get_key(val): 25 | for key, value in patho.items(): 26 | if val == value: 27 | return key 28 | 29 | # Adjusted saving part 30 | for i in range(len(Xsmote_img)): 31 | # Check if the label is 'malignant' 32 | if y_smote[i] == patho['malignant']: 33 | # Define the directory for malignant images 34 | malignant_dir = '/home/vuk/Documents/ML_Data/HAM/processed/roi/malignant/' 35 | 36 | # Check if the directory exists, if not, create it 37 | if not os.path.exists(malignant_dir): 38 | os.mkdir(malignant_dir) 39 | 40 | # Convert the image array to a PIL image 41 | pil_img = array_to_img(Xsmote_img[i] * 255) 42 | 43 | # Save the image with a naming convention that reflects their index in the oversampled dataset 44 | pil_img.save(malignant_dir + 'smote_' + str(1293 + i) + '.jpg') -------------------------------------------------------------------------------- /model/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | # Comments are provided throughout this file to help you get started. 4 | # If you need more help, visit the Dockerfile reference guide at 5 | # https://docs.docker.com/go/dockerfile-reference/ 6 | 7 | # Want to help us make this template better? Share your feedback here: https://forms.gle/ybq9Krt8jtBL3iCk7 8 | 9 | ARG PYTHON_VERSION=3.11.5 10 | FROM python:${PYTHON_VERSION}-slim as base 11 | 12 | # Prevents Python from writing pyc files. 13 | ENV PYTHONDONTWRITEBYTECODE=1 14 | 15 | # Keeps Python from buffering stdout and stderr to avoid situations where 16 | # the application crashes without emitting any logs due to buffering. 17 | ENV PYTHONUNBUFFERED=1 18 | 19 | WORKDIR /app 20 | 21 | # Create a non-privileged user that the app will run under. 22 | # See https://docs.docker.com/go/dockerfile-user-best-practices/ 23 | ARG UID=10001 24 | RUN adduser \ 25 | --disabled-password \ 26 | --gecos "" \ 27 | --home "/nonexistent" \ 28 | --shell "/sbin/nologin" \ 29 | --no-create-home \ 30 | --uid "${UID}" \ 31 | appuser 32 | 33 | # Download dependencies as a separate step to take advantage of Docker's caching. 34 | # Leverage a cache mount to /root/.cache/pip to speed up subsequent builds. 35 | # Leverage a bind mount to requirements.txt to avoid having to copy them into 36 | # into this layer. 37 | RUN --mount=type=cache,target=/root/.cache/pip \ 38 | --mount=type=bind,source=requirements.txt,target=requirements.txt \ 39 | python -m pip install -r requirements.txt 40 | 41 | # Switch to the non-privileged user to run the application. 42 | USER appuser 43 | 44 | # Copy the source code into the container. 45 | COPY . . 46 | 47 | # Expose the port that the application listens on. 48 | EXPOSE 8000 49 | 50 | # Run the application. 51 | CMD /home/vuk/miniconda3/bin/python /home/vuk/Melanoma-Detector/model/fusion_stage.py 52 | -------------------------------------------------------------------------------- /client/src/screens/DiagnosisScreen.jsx: -------------------------------------------------------------------------------- 1 | import { View, Text, Image } from 'react-native'; 2 | import { BaseScreen } from '../components/common/BaseScreen'; 3 | import { useContext } from 'react'; 4 | import PermissionsContext from '../context/PermissionsContext'; 5 | import PrimaryButton from '../components/button/PrimaryButton'; 6 | import SecondaryButton from '../components/button/SecondaryButton'; 7 | import { Ionicons } from '@expo/vector-icons'; 8 | import ImageContext from '../context/ImageContext'; 9 | import { useGlobalStyle } from '../hooks/useGlobalStyle'; 10 | import { scale, scaleImage } from '../helpers/scale'; 11 | import Footer from '../components/Footer'; 12 | 13 | const DiagnosisScreen = () => { 14 | const { imgUri } = useContext(ImageContext); 15 | const basicStyles = useGlobalStyle(); 16 | 17 | const { width: scaledWidth, height: scaledHeight } = scaleImage( 18 | 100, 19 | 100, 20 | 340, 21 | ); 22 | 23 | return ( 24 | 25 | 26 | 27 | 40 | 41 | Your naveus is probably NOT cancerous 42 | 43 | 44 | Disclaimer: This model makes mistakes and is NOT* a replacement for 45 | a doctor, if you have serious doubts make sure to contact your 46 | dermatologist. 47 | 48 | 49 | 50 |