2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
// createGeneticModels.go provides an interface to create genetic prediction models
|
2024-08-13 15:25:47 +02:00
|
|
|
// These are neural networks which predict attributes such as eye color and autism from raw genome files
|
2024-04-11 15:51:56 +02:00
|
|
|
// The OpenSNP.org dataset is used, and more datasets will be added in the future.
|
|
|
|
// You must download the dataset and extract it. The instructions are described in the utility.
|
2024-08-15 14:14:23 +02:00
|
|
|
// The trained models are saved in the /resources/trainedPredictionModels package for use in the Seekia app.
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import "fyne.io/fyne/v2"
|
|
|
|
import "fyne.io/fyne/v2/app"
|
|
|
|
import "fyne.io/fyne/v2/widget"
|
|
|
|
import "fyne.io/fyne/v2/container"
|
|
|
|
import "fyne.io/fyne/v2/theme"
|
|
|
|
import "fyne.io/fyne/v2/layout"
|
|
|
|
import "fyne.io/fyne/v2/dialog"
|
|
|
|
import "fyne.io/fyne/v2/data/binding"
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
import "seekia/resources/geneticReferences/polygenicDiseases"
|
2024-04-11 15:51:56 +02:00
|
|
|
import "seekia/resources/geneticReferences/traits"
|
|
|
|
import "seekia/resources/geneticReferences/locusMetadata"
|
2024-08-15 14:14:23 +02:00
|
|
|
import "seekia/resources/trainedPredictionModels"
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
import "seekia/internal/encoding"
|
|
|
|
import "seekia/internal/genetics/locusValue"
|
|
|
|
import "seekia/internal/genetics/prepareRawGenomes"
|
|
|
|
import "seekia/internal/genetics/readRawGenomes"
|
|
|
|
import "seekia/internal/genetics/geneticPrediction"
|
2024-08-15 14:14:23 +02:00
|
|
|
import "seekia/internal/genetics/geneticPredictionModels"
|
2024-08-13 15:25:47 +02:00
|
|
|
import "seekia/internal/globalSettings"
|
2024-04-11 15:51:56 +02:00
|
|
|
import "seekia/internal/helpers"
|
|
|
|
import "seekia/internal/imagery"
|
|
|
|
import "seekia/internal/localFilesystem"
|
|
|
|
import "seekia/internal/genetics/readBiobankData"
|
|
|
|
|
|
|
|
import "errors"
|
|
|
|
import "crypto/sha256"
|
|
|
|
import "bytes"
|
|
|
|
import "image/color"
|
|
|
|
import "io"
|
|
|
|
import "os"
|
|
|
|
import "strings"
|
|
|
|
import "sync"
|
|
|
|
import "slices"
|
2024-08-07 09:45:31 +02:00
|
|
|
import "math"
|
2024-05-02 04:45:00 +02:00
|
|
|
import mathRand "math/rand/v2"
|
2024-04-11 15:51:56 +02:00
|
|
|
import goFilepath "path/filepath"
|
2024-06-15 13:02:31 +02:00
|
|
|
import "time"
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-04-11 15:51:56 +02:00
|
|
|
func main(){
|
|
|
|
|
2024-08-14 05:37:18 +02:00
|
|
|
err := polygenicDiseases.InitializePolygenicDiseaseVariables()
|
|
|
|
if (err != nil){
|
|
|
|
panic(err)
|
|
|
|
return
|
|
|
|
}
|
2024-08-13 15:25:47 +02:00
|
|
|
|
2024-08-14 05:37:18 +02:00
|
|
|
err = traits.InitializeTraitVariables()
|
2024-08-05 09:11:10 +02:00
|
|
|
if (err != nil){
|
|
|
|
panic(err)
|
|
|
|
return
|
|
|
|
}
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
err = globalSettings.InitializeGlobalSettingsDatastore()
|
|
|
|
if (err != nil){
|
|
|
|
panic(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-04-11 15:51:56 +02:00
|
|
|
app := app.New()
|
|
|
|
|
|
|
|
customTheme := getCustomFyneTheme()
|
|
|
|
|
|
|
|
app.Settings().SetTheme(customTheme)
|
|
|
|
|
|
|
|
window := app.NewWindow("Seekia - Create Genetic Models Utility")
|
|
|
|
|
|
|
|
windowSize := fyne.NewSize(600, 600)
|
|
|
|
window.Resize(windowSize)
|
|
|
|
|
|
|
|
window.CenterOnScreen()
|
|
|
|
|
|
|
|
setHomePage(window)
|
|
|
|
|
|
|
|
window.ShowAndRun()
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func getWidgetCentered(widget fyne.Widget)*fyne.Container{
|
|
|
|
|
|
|
|
widgetCentered := container.NewHBox(layout.NewSpacer(), widget, layout.NewSpacer())
|
|
|
|
|
|
|
|
return widgetCentered
|
|
|
|
}
|
|
|
|
|
|
|
|
func getLabelCentered(text string) *fyne.Container{
|
|
|
|
|
|
|
|
label := widget.NewLabel(text)
|
|
|
|
labelCentered := container.NewHBox(layout.NewSpacer(), label, layout.NewSpacer())
|
|
|
|
|
|
|
|
return labelCentered
|
|
|
|
}
|
|
|
|
|
|
|
|
func getBoldLabel(text string) fyne.Widget{
|
|
|
|
|
|
|
|
titleStyle := fyne.TextStyle{
|
|
|
|
Bold: true,
|
|
|
|
Italic: false,
|
|
|
|
Monospace: false,
|
|
|
|
}
|
|
|
|
|
|
|
|
boldLabel := widget.NewLabelWithStyle(text, fyne.TextAlign(fyne.TextAlignCenter), titleStyle)
|
|
|
|
|
|
|
|
return boldLabel
|
|
|
|
}
|
|
|
|
|
|
|
|
func getItalicLabel(text string) fyne.Widget{
|
|
|
|
|
|
|
|
italicTextStyle := fyne.TextStyle{
|
|
|
|
Bold: false,
|
|
|
|
Italic: true,
|
|
|
|
Monospace: false,
|
|
|
|
}
|
|
|
|
|
|
|
|
italicLabel := widget.NewLabelWithStyle(text, fyne.TextAlign(fyne.TextAlignCenter), italicTextStyle)
|
|
|
|
|
|
|
|
return italicLabel
|
|
|
|
}
|
|
|
|
|
|
|
|
func getBoldLabelCentered(inputText string)*fyne.Container{
|
|
|
|
|
|
|
|
boldLabel := getBoldLabel(inputText)
|
|
|
|
|
|
|
|
boldLabelCentered := container.NewHBox(layout.NewSpacer(), boldLabel, layout.NewSpacer())
|
|
|
|
|
|
|
|
return boldLabelCentered
|
|
|
|
}
|
|
|
|
|
|
|
|
func getItalicLabelCentered(inputText string)*fyne.Container{
|
|
|
|
|
|
|
|
italicLabel := getItalicLabel(inputText)
|
|
|
|
|
|
|
|
italicLabelCentered := container.NewHBox(layout.NewSpacer(), italicLabel, layout.NewSpacer())
|
|
|
|
|
|
|
|
return italicLabelCentered
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func showUnderConstructionDialog(window fyne.Window){
|
|
|
|
|
|
|
|
dialogTitle := "Under Construction"
|
|
|
|
dialogMessageA := getLabelCentered("Seekia is under construction.")
|
|
|
|
dialogMessageB := getLabelCentered("This page/feature needs to be built.")
|
|
|
|
dialogContent := container.NewVBox(dialogMessageA, dialogMessageB)
|
|
|
|
dialog.ShowCustom(dialogTitle, "Close", dialogContent, window)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func getBackButtonCentered(previousPage func())*fyne.Container{
|
|
|
|
|
|
|
|
backButton := getWidgetCentered(widget.NewButtonWithIcon("Go Back", theme.NavigateBackIcon(), previousPage))
|
|
|
|
|
|
|
|
return backButton
|
|
|
|
}
|
|
|
|
|
|
|
|
func setErrorEncounteredPage(window fyne.Window, err error, previousPage func()){
|
|
|
|
|
|
|
|
title := getBoldLabelCentered("Error Encountered")
|
|
|
|
|
|
|
|
backButton := getBackButtonCentered(previousPage)
|
|
|
|
|
|
|
|
description1 := getLabelCentered("Something went wrong. Report this error to Seekia developers.")
|
|
|
|
|
|
|
|
header := container.NewVBox(title, backButton, widget.NewSeparator(), description1, widget.NewSeparator())
|
|
|
|
|
|
|
|
getErrorString := func()string{
|
|
|
|
if (err == nil){
|
|
|
|
return "No nav bar error encountered page called with nil error."
|
|
|
|
}
|
|
|
|
errorString := err.Error()
|
|
|
|
return errorString
|
|
|
|
}
|
|
|
|
|
|
|
|
errorString := getErrorString()
|
|
|
|
|
|
|
|
errorLabel := widget.NewLabel(errorString)
|
|
|
|
errorLabel.Wrapping = 3
|
|
|
|
errorLabel.Alignment = 1
|
|
|
|
errorLabel.TextStyle = fyne.TextStyle{
|
|
|
|
Bold: true,
|
|
|
|
Italic: false,
|
|
|
|
Monospace: false,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//TODO: Add copyable toggle
|
|
|
|
|
|
|
|
page := container.NewBorder(header, nil, nil, nil, errorLabel)
|
|
|
|
|
|
|
|
window.SetContent(page)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// This loading screen shows no progress, so it should only be used when retrieving progress is impossible
|
|
|
|
func setLoadingScreen(window fyne.Window, pageTitle string, loadingText string){
|
|
|
|
|
|
|
|
title := getBoldLabelCentered(pageTitle)
|
|
|
|
|
|
|
|
loadingLabel := getWidgetCentered(getItalicLabel(loadingText))
|
|
|
|
progressBar := getWidgetCentered(widget.NewProgressBarInfinite())
|
|
|
|
|
|
|
|
pageContent := container.NewVBox(title, loadingLabel, progressBar)
|
|
|
|
|
|
|
|
page := container.NewCenter(pageContent)
|
|
|
|
|
|
|
|
window.SetContent(page)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func setHomePage(window fyne.Window){
|
|
|
|
|
|
|
|
currentPage := func(){setHomePage(window)}
|
|
|
|
|
|
|
|
title := getBoldLabelCentered("Create Genetic Models Utility")
|
|
|
|
|
|
|
|
description1 := getLabelCentered("This utility is used to create the genetic prediction models.")
|
2024-08-13 15:25:47 +02:00
|
|
|
description2 := getLabelCentered("These models are used to predict attributes such as eye color and autism from raw genome files.")
|
2024-04-11 15:51:56 +02:00
|
|
|
description3 := getLabelCentered("Seekia aims to have open source and reproducible genetic prediction technology.")
|
|
|
|
|
|
|
|
step1Label := getLabelCentered("Step 1:")
|
|
|
|
|
|
|
|
downloadTrainingDataButton := getWidgetCentered(widget.NewButton("Download Training Data", func(){
|
|
|
|
setDownloadTrainingDataPage(window, currentPage)
|
|
|
|
}))
|
|
|
|
|
|
|
|
step2Label := getLabelCentered("Step 2:")
|
|
|
|
|
|
|
|
extractTrainingDataButton := getWidgetCentered(widget.NewButton("Extract Training Data", func(){
|
|
|
|
setExtractTrainingDataPage(window, currentPage)
|
|
|
|
}))
|
|
|
|
|
|
|
|
step3Label := getLabelCentered("Step 3:")
|
|
|
|
|
|
|
|
createTrainingDataButton := getWidgetCentered(widget.NewButton("Create Training Data", func(){
|
|
|
|
setCreateTrainingDataPage(window, currentPage)
|
|
|
|
}))
|
|
|
|
|
|
|
|
step4Label := getLabelCentered("Step 4:")
|
|
|
|
|
|
|
|
trainModelsButton := getWidgetCentered(widget.NewButton("Train Models", func(){
|
|
|
|
setTrainModelsPage(window, currentPage)
|
|
|
|
}))
|
|
|
|
|
|
|
|
step5Label := getLabelCentered("Step 5:")
|
|
|
|
|
|
|
|
testModelsButton := getWidgetCentered(widget.NewButton("Test Models", func(){
|
|
|
|
setTestModelsPage(window, currentPage)
|
|
|
|
}))
|
|
|
|
|
|
|
|
//TODO: A page to verify the checksums of the generated .gob models
|
|
|
|
|
|
|
|
page := container.NewVBox(title, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), step1Label, downloadTrainingDataButton, widget.NewSeparator(), step2Label, extractTrainingDataButton, widget.NewSeparator(), step3Label, createTrainingDataButton, widget.NewSeparator(), step4Label, trainModelsButton, widget.NewSeparator(), step5Label, testModelsButton)
|
|
|
|
|
|
|
|
window.SetContent(page)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func setDownloadTrainingDataPage(window fyne.Window, previousPage func()){
|
|
|
|
|
|
|
|
currentPage := func(){setDownloadTrainingDataPage(window, previousPage)}
|
|
|
|
|
|
|
|
title := getBoldLabelCentered("Download Training Data")
|
|
|
|
|
|
|
|
backButton := getBackButtonCentered(previousPage)
|
|
|
|
|
|
|
|
description1 := getLabelCentered("You must download the OpenSNP.org data dump file.")
|
|
|
|
description2 := getLabelCentered("This is a .tar.gz file which was created in August of 2023.")
|
|
|
|
description3 := getLabelCentered("It will be hosted on IPFS, a decentralized data sharing network.")
|
|
|
|
description4 := getLabelCentered("You must use an IPFS client to download the file.")
|
|
|
|
description5 := getLabelCentered("You can also download it via a torrent or web server if someone shares it elsewhere.")
|
|
|
|
|
|
|
|
currentClipboard := window.Clipboard()
|
|
|
|
|
|
|
|
ipfsIdentifierTitle := getLabelCentered("IPFS Content Identifier:")
|
|
|
|
|
|
|
|
ipfsIdentifierLabel := getBoldLabelCentered("Qme64v7Go941s3psokZ7aDngQR6Tdv55jDhUDdLZXsRiRh")
|
|
|
|
|
|
|
|
ipfsIdentifierCopyToClipboardButton := getWidgetCentered(widget.NewButtonWithIcon("Copy", theme.ContentCopyIcon(), func(){
|
|
|
|
|
|
|
|
currentClipboard.SetContent("Qme64v7Go941s3psokZ7aDngQR6Tdv55jDhUDdLZXsRiRh")
|
|
|
|
}))
|
|
|
|
|
|
|
|
fileNameTitle := getLabelCentered("File Name:")
|
|
|
|
|
|
|
|
fileNameLabel := getBoldLabelCentered("OpenSNPDataArchive.tar.gz")
|
|
|
|
|
|
|
|
fileHashTitle := getLabelCentered("File SHA256 Checksum Hash:")
|
|
|
|
|
|
|
|
fileHashLabel := getBoldLabelCentered("49f84fb71cb12df718a80c1ce25f6370ba758cbee8f24bd8a6d4f0da2e3c51ee")
|
|
|
|
|
|
|
|
fileSizeTitle := getLabelCentered("File Size:")
|
|
|
|
|
|
|
|
fileSizeLabel := getBoldLabelCentered("48,961,240 bytes (50.1 GB)")
|
|
|
|
|
|
|
|
fileExtractedSizeTitle := getLabelCentered("File Extracted Size:")
|
|
|
|
|
|
|
|
fileExtractedSizeLabel := getBoldLabelCentered("128,533,341,751 bytes (119.7 GB)")
|
|
|
|
|
|
|
|
verifyFileTitle := getBoldLabelCentered("Verify File")
|
|
|
|
|
|
|
|
verifyFileDescription1 := getLabelCentered("You can use the Seekia client to verify your downloaded file.")
|
|
|
|
verifyFileDescription2 := getLabelCentered("Press the button below and select your file.")
|
|
|
|
verifyFileDescription3 := getLabelCentered("This will take a while, because the file contents must be hashed.")
|
|
|
|
|
|
|
|
selectFileCallbackFunction := func(fyneFileObject fyne.URIReadCloser, err error){
|
|
|
|
|
|
|
|
if (err != nil){
|
|
|
|
setErrorEncounteredPage(window, err, currentPage)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fyneFileObject == nil){
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
setLoadingScreen(window, "Hashing File", "Calculating file hash...")
|
|
|
|
|
|
|
|
filePath := fyneFileObject.URI().String()
|
|
|
|
filePath = strings.TrimPrefix(filePath, "file://")
|
|
|
|
|
|
|
|
fileObject, err := os.Open(filePath)
|
|
|
|
if (err != nil){
|
|
|
|
setErrorEncounteredPage(window, err, currentPage)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer fileObject.Close()
|
|
|
|
|
|
|
|
//TODO: Use Blake3 instead of sha256 for faster hashing
|
|
|
|
|
|
|
|
hasher := sha256.New()
|
|
|
|
_, err = io.Copy(hasher, fileObject)
|
|
|
|
if (err != nil){
|
|
|
|
setErrorEncounteredPage(window, err, currentPage)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
hashResultBytes := hasher.Sum(nil)
|
|
|
|
|
|
|
|
expectedResult := "49f84fb71cb12df718a80c1ce25f6370ba758cbee8f24bd8a6d4f0da2e3c51ee"
|
|
|
|
|
|
|
|
expectedResultBytes, err := encoding.DecodeHexStringToBytes(expectedResult)
|
|
|
|
if (err != nil){
|
|
|
|
setErrorEncounteredPage(window, err, currentPage)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
currentPage()
|
|
|
|
|
|
|
|
bytesAreEqual := bytes.Equal(hashResultBytes, expectedResultBytes)
|
|
|
|
if (bytesAreEqual == false){
|
|
|
|
title := "File Is Invalid"
|
|
|
|
dialogMessage1 := getLabelCentered("The file you downloaded is not valid.")
|
|
|
|
dialogMessage2 := getLabelCentered("The SHA256 Checksum does not match the expected checksum.")
|
|
|
|
dialogContent := container.NewVBox(dialogMessage1, dialogMessage2)
|
|
|
|
dialog.ShowCustom(title, "Close", dialogContent, window)
|
|
|
|
} else {
|
|
|
|
title := "File Is Valid"
|
|
|
|
dialogMessage1 := getLabelCentered("The file you downloaded is valid!")
|
|
|
|
dialogMessage2 := getLabelCentered("The SHA256 Checksum matches the expected checksum.")
|
|
|
|
dialogContent := container.NewVBox(dialogMessage1, dialogMessage2)
|
|
|
|
dialog.ShowCustom(title, "Close", dialogContent, window)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
verifyFileButton := getWidgetCentered(widget.NewButton("Verify File", func(){
|
|
|
|
dialog.ShowFileOpen(selectFileCallbackFunction, window)
|
|
|
|
}))
|
|
|
|
|
|
|
|
page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, description4, description5, widget.NewSeparator(), ipfsIdentifierTitle, ipfsIdentifierLabel, ipfsIdentifierCopyToClipboardButton, widget.NewSeparator(), fileNameTitle, fileNameLabel, widget.NewSeparator(), fileHashTitle, fileHashLabel, widget.NewSeparator(), fileSizeTitle, fileSizeLabel, widget.NewSeparator(), fileExtractedSizeTitle, fileExtractedSizeLabel, widget.NewSeparator(), verifyFileTitle, verifyFileDescription1, verifyFileDescription2, verifyFileDescription3, verifyFileButton)
|
|
|
|
|
|
|
|
scrollablePage := container.NewVScroll(page)
|
|
|
|
|
|
|
|
window.SetContent(scrollablePage)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func setExtractTrainingDataPage(window fyne.Window, previousPage func()){
|
|
|
|
|
|
|
|
currentPage := func(){setExtractTrainingDataPage(window, previousPage)}
|
|
|
|
|
|
|
|
title := getBoldLabelCentered("Extract Training Data")
|
|
|
|
|
|
|
|
backButton := getBackButtonCentered(previousPage)
|
|
|
|
|
|
|
|
description1 := getLabelCentered("You must extract the downloaded OpenSNPDataArchive.tar.gz to a folder.")
|
|
|
|
description2 := getLabelCentered("Once you have extracted the file, select the extracted folder using the page below.")
|
|
|
|
|
|
|
|
currentLocationTitle := getLabelCentered("Current Folder Location:")
|
|
|
|
|
|
|
|
getCurrentLocationLabel := func()(*fyne.Container, error){
|
|
|
|
|
|
|
|
fileExists, fileContents, err := localFilesystem.GetFileContents("./OpenSNPDataArchiveFolderpath.txt")
|
|
|
|
if (err != nil) { return nil, err }
|
|
|
|
if (fileExists == false){
|
|
|
|
noneLabel := getItalicLabelCentered("None")
|
|
|
|
return noneLabel, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
folderpathLabel := getBoldLabelCentered(string(fileContents))
|
|
|
|
|
|
|
|
return folderpathLabel, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
currentLocationLabel, err := getCurrentLocationLabel()
|
|
|
|
if (err != nil) {
|
|
|
|
setErrorEncounteredPage(window, err, previousPage)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
selectFolderCallbackFunction := func(folderObject fyne.ListableURI, err error){
|
|
|
|
|
|
|
|
if (err != nil){
|
|
|
|
title := "Failed to open folder."
|
|
|
|
dialogMessage := getLabelCentered("Report this error to Seekia developers: " + err.Error())
|
|
|
|
dialogContent := container.NewVBox(dialogMessage)
|
|
|
|
dialog.ShowCustom(title, "Close", dialogContent, window)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (folderObject == nil) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
folderPath := folderObject.Path()
|
|
|
|
|
|
|
|
fileContents := []byte(folderPath)
|
|
|
|
|
|
|
|
err = localFilesystem.CreateOrOverwriteFile(fileContents, "./", "OpenSNPDataArchiveFolderpath.txt")
|
|
|
|
if (err != nil){
|
|
|
|
title := "Failed to save file."
|
|
|
|
dialogMessage := getLabelCentered("Report this error to Seekia developers: " + err.Error())
|
|
|
|
dialogContent := container.NewVBox(dialogMessage)
|
|
|
|
dialog.ShowCustom(title, "Close", dialogContent, window)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
currentPage()
|
|
|
|
}
|
|
|
|
|
|
|
|
selectFolderLocationButton := getWidgetCentered(widget.NewButtonWithIcon("Select Folder Location", theme.FolderIcon(), func(){
|
|
|
|
dialog.ShowFolderOpen(selectFolderCallbackFunction, window)
|
|
|
|
}))
|
|
|
|
|
|
|
|
page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, widget.NewSeparator(), currentLocationTitle, currentLocationLabel, widget.NewSeparator(), selectFolderLocationButton)
|
|
|
|
|
|
|
|
scrollablePage := container.NewVScroll(page)
|
|
|
|
|
|
|
|
window.SetContent(scrollablePage)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func setCreateTrainingDataPage(window fyne.Window, previousPage func()){
|
|
|
|
|
|
|
|
currentPage := func(){setCreateTrainingDataPage(window, previousPage)}
|
|
|
|
|
|
|
|
title := getBoldLabelCentered("Create Training Data")
|
|
|
|
|
|
|
|
backButton := getBackButtonCentered(previousPage)
|
|
|
|
|
|
|
|
description1 := getLabelCentered("Press the button below to begin creating the training data.")
|
|
|
|
description2 := getLabelCentered("This will prepare each user's genome into a file to use to train each neural network.")
|
|
|
|
description3 := getLabelCentered("This will take a while.")
|
|
|
|
|
|
|
|
beginCreatingButton := getWidgetCentered(widget.NewButtonWithIcon("Begin Creating Data", theme.MediaPlayIcon(), func(){
|
|
|
|
setStartAndMonitorCreateTrainingDataPage(window, currentPage)
|
|
|
|
}))
|
|
|
|
|
|
|
|
page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, beginCreatingButton)
|
|
|
|
|
|
|
|
window.SetContent(page)
|
|
|
|
}
|
|
|
|
|
|
|
|
func setStartAndMonitorCreateTrainingDataPage(window fyne.Window, previousPage func()){
|
|
|
|
|
|
|
|
err := locusMetadata.InitializeLocusMetadataVariables()
|
|
|
|
if (err != nil){
|
|
|
|
setErrorEncounteredPage(window, err, previousPage)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
title := getBoldLabelCentered("Creating Training Data")
|
|
|
|
|
|
|
|
fileExists, fileContents, err := localFilesystem.GetFileContents("./OpenSNPDataArchiveFolderpath.txt")
|
|
|
|
if (err != nil) {
|
|
|
|
setErrorEncounteredPage(window, err, previousPage)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (fileExists == false){
|
|
|
|
|
|
|
|
backButton := getBackButtonCentered(previousPage)
|
|
|
|
|
|
|
|
description1 := getBoldLabelCentered("You have not selected your OpenSNP data archive folderpath.")
|
|
|
|
description2 := getLabelCentered("Go back to step 2 and follow the instructions.")
|
|
|
|
|
|
|
|
page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2)
|
|
|
|
|
|
|
|
window.SetContent(page)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
dataArchiveFolderpath := string(fileContents)
|
|
|
|
|
|
|
|
progressDetailsBinding := binding.NewString()
|
2024-06-15 13:02:31 +02:00
|
|
|
estimatedTimeRemainingBinding := binding.NewString()
|
2024-04-11 15:51:56 +02:00
|
|
|
progressPercentageBinding := binding.NewFloat()
|
|
|
|
|
|
|
|
loadingBar := getWidgetCentered(widget.NewProgressBarWithData(progressPercentageBinding))
|
|
|
|
|
|
|
|
progressDetailsTitle := getBoldLabelCentered("Progress Details:")
|
|
|
|
|
|
|
|
progressDetailsLabel := widget.NewLabelWithData(progressDetailsBinding)
|
|
|
|
progressDetailsLabel.TextStyle = fyne.TextStyle{
|
|
|
|
Bold: false,
|
|
|
|
Italic: true,
|
|
|
|
Monospace: false,
|
|
|
|
}
|
|
|
|
|
|
|
|
progressDetailsLabelCentered := getWidgetCentered(progressDetailsLabel)
|
|
|
|
|
2024-06-15 13:02:31 +02:00
|
|
|
estimatedTimeRemainingLabel := widget.NewLabelWithData(estimatedTimeRemainingBinding)
|
|
|
|
estimatedTimeRemainingLabel.TextStyle = fyne.TextStyle{
|
|
|
|
Bold: false,
|
|
|
|
Italic: true,
|
|
|
|
Monospace: false,
|
|
|
|
}
|
|
|
|
|
|
|
|
estimatedTimeRemainingLabelCentered := getWidgetCentered(estimatedTimeRemainingLabel)
|
|
|
|
|
2024-04-11 15:51:56 +02:00
|
|
|
// We set this bool to true to stop the createData process
|
|
|
|
var createDataIsStoppedBoolMutex sync.RWMutex
|
|
|
|
createDataIsStoppedBool := false
|
|
|
|
|
|
|
|
cancelButton := getWidgetCentered(widget.NewButtonWithIcon("Cancel", theme.CancelIcon(), func(){
|
|
|
|
|
|
|
|
createDataIsStoppedBoolMutex.Lock()
|
|
|
|
createDataIsStoppedBool = true
|
|
|
|
createDataIsStoppedBoolMutex.Unlock()
|
|
|
|
|
|
|
|
previousPage()
|
|
|
|
}))
|
|
|
|
|
2024-06-15 13:02:31 +02:00
|
|
|
page := container.NewVBox(title, widget.NewSeparator(), loadingBar, progressDetailsTitle, progressDetailsLabelCentered, estimatedTimeRemainingLabelCentered, widget.NewSeparator(), cancelButton)
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
window.SetContent(page)
|
|
|
|
|
|
|
|
createTrainingDataFunction := func(){
|
|
|
|
|
2024-06-15 13:02:31 +02:00
|
|
|
var processProgressMutex sync.RWMutex
|
|
|
|
// This stores the progress of creating the training data (0-1)
|
|
|
|
processProgress := float64(0)
|
|
|
|
|
|
|
|
startUpdateTimeRemainingDisplayFunction := func(){
|
|
|
|
|
|
|
|
// This function updates the estimated time remaining label binding
|
|
|
|
|
|
|
|
updateTimeRemainingDisplayFunction := func()error{
|
|
|
|
|
|
|
|
startTime := time.Now().Unix()
|
|
|
|
|
|
|
|
for{
|
|
|
|
|
|
|
|
createDataIsStoppedBoolMutex.RLock()
|
|
|
|
createDataIsStopped := createDataIsStoppedBool
|
|
|
|
createDataIsStoppedBoolMutex.RUnlock()
|
|
|
|
|
|
|
|
if (createDataIsStopped == true){
|
|
|
|
// User exited the process/Process has completed
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
processProgressMutex.RLock()
|
|
|
|
currentProcessProgress := processProgress
|
|
|
|
processProgressMutex.RUnlock()
|
|
|
|
|
|
|
|
if (currentProcessProgress == 0){
|
|
|
|
estimatedTimeRemainingBinding.Set("Calculating required time...")
|
|
|
|
time.Sleep(time.Second)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// We calculate how long we think it will take for the process to complete
|
|
|
|
|
|
|
|
currentTime := time.Now().Unix()
|
|
|
|
|
|
|
|
secondsElapsed := currentTime - startTime
|
|
|
|
|
|
|
|
// processProgress is a float64 which stores the progress as a value between 0-1
|
|
|
|
// To get the estimated total time the process will take, we divide the seconds elapsed by the proportion of progress
|
|
|
|
// For example:
|
|
|
|
// 0.1 (10%) at 10 seconds == Total process will take 100 seconds
|
|
|
|
// 0.5 (50%) at 20 seconds == Total process will take 40 seconds
|
|
|
|
|
|
|
|
totalSeconds := float64(secondsElapsed)/currentProcessProgress
|
|
|
|
|
|
|
|
estimatedSecondsRemaining := int64(totalSeconds) - secondsElapsed
|
|
|
|
|
|
|
|
estimatedTimeRemainingTranslated, err := helpers.ConvertUnixTimeDurationToUnitsTimeTranslated(estimatedSecondsRemaining, false)
|
|
|
|
if (err != nil) { return err }
|
|
|
|
|
|
|
|
estimatedTimeRemainingBinding.Set("Estimated Time Remaining: " + estimatedTimeRemainingTranslated)
|
|
|
|
|
|
|
|
time.Sleep(time.Second)
|
|
|
|
}
|
|
|
|
|
|
|
|
// This should never be reached
|
|
|
|
|
|
|
|
return errors.New("updateTimeRemainingDisplayFunction loop has broken.")
|
|
|
|
}
|
|
|
|
|
|
|
|
err := updateTimeRemainingDisplayFunction()
|
|
|
|
if (err != nil){
|
|
|
|
|
|
|
|
createDataIsStoppedBoolMutex.Lock()
|
|
|
|
createDataIsStoppedBool = true
|
|
|
|
createDataIsStoppedBoolMutex.Unlock()
|
|
|
|
|
|
|
|
setErrorEncounteredPage(window, err, previousPage)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
go startUpdateTimeRemainingDisplayFunction()
|
|
|
|
|
2024-04-11 15:51:56 +02:00
|
|
|
//Outputs:
|
|
|
|
// -bool: Process completed (true == was not stopped mid-way)
|
|
|
|
// -bool: Data archive is well formed
|
|
|
|
// -error
|
|
|
|
createTrainingData := func()(bool, bool, error){
|
|
|
|
|
|
|
|
phenotypesFilepath := goFilepath.Join(dataArchiveFolderpath, "OpenSNPData", "phenotypes_202308230100.csv")
|
|
|
|
|
|
|
|
fileObject, err := os.Open(phenotypesFilepath)
|
|
|
|
if (err != nil){
|
|
|
|
|
|
|
|
fileDoesNotExist := os.IsNotExist(err)
|
|
|
|
if (fileDoesNotExist == true){
|
|
|
|
// Archive is corrupt
|
|
|
|
return true, false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return false, false, err
|
|
|
|
}
|
|
|
|
defer fileObject.Close()
|
|
|
|
|
|
|
|
fileIsWellFormed, userPhenotypesList_OpenSNP := readBiobankData.ReadOpenSNPPhenotypesFile(fileObject)
|
|
|
|
if (fileIsWellFormed == false){
|
|
|
|
// Archive is corrupt
|
|
|
|
return true, false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is the folderpath for the folder which contains all of the user raw genomes
|
|
|
|
openSNPRawGenomesFolderpath := goFilepath.Join(dataArchiveFolderpath, "OpenSNPData")
|
|
|
|
|
|
|
|
filesList, err := os.ReadDir(openSNPRawGenomesFolderpath)
|
|
|
|
if (err != nil) { return false, false, err }
|
|
|
|
|
|
|
|
// Map Structure: User ID -> List of user raw genome filepaths
|
|
|
|
userRawGenomeFilepathsMap := make(map[int][]string)
|
|
|
|
|
|
|
|
for _, filesystemObject := range filesList{
|
|
|
|
|
|
|
|
filepathIsFolder := filesystemObject.IsDir()
|
|
|
|
if (filepathIsFolder == true){
|
|
|
|
// Archive is corrupt
|
|
|
|
return true, false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
fileName := filesystemObject.Name()
|
|
|
|
|
|
|
|
// Example of a raw genome filename: "user1_file9_yearofbirth_1985_sex_XY.23andme"
|
|
|
|
|
|
|
|
userIDWithRawGenomeInfo, fileIsUserGenome := strings.CutPrefix(fileName, "user")
|
|
|
|
if (fileIsUserGenome == false){
|
|
|
|
// File is not a user genome, skip it.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
userIDString, rawGenomeInfo, separatorFound := strings.Cut(userIDWithRawGenomeInfo, "_")
|
|
|
|
if (separatorFound == false){
|
|
|
|
// Archive is corrupt
|
|
|
|
return true, false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
userID, err := helpers.ConvertStringToInt(userIDString)
|
|
|
|
if (err != nil){
|
|
|
|
// Archive is corrupt
|
|
|
|
return true, false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
getFileIsReadableStatus := func()bool{
|
|
|
|
|
|
|
|
is23andMe := strings.HasSuffix(rawGenomeInfo, ".23andme.txt")
|
|
|
|
if (is23andMe == true){
|
|
|
|
// We can read this file
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
isAncestry := strings.HasSuffix(rawGenomeInfo, ".ancestry.txt")
|
|
|
|
if (isAncestry == true){
|
|
|
|
// We can read this file
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// We cannot read this raw genome file
|
|
|
|
//TODO: Add ability to read more raw genome files
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
fileIsReadable := getFileIsReadableStatus()
|
|
|
|
if (fileIsReadable == true){
|
|
|
|
|
|
|
|
rawGenomeFilepath := goFilepath.Join(openSNPRawGenomesFolderpath, fileName)
|
|
|
|
|
|
|
|
existingList, exists := userRawGenomeFilepathsMap[userID]
|
|
|
|
if (exists == false){
|
|
|
|
userRawGenomeFilepathsMap[userID] = []string{rawGenomeFilepath}
|
|
|
|
} else {
|
|
|
|
existingList = append(existingList, rawGenomeFilepath)
|
|
|
|
userRawGenomeFilepathsMap[userID] = existingList
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
// We create the folder to store the training data
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
_, err = localFilesystem.CreateFolder("./TrainingData")
|
|
|
|
if (err != nil) { return false, false, err }
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
//TODO: Add more attributes
|
2024-08-14 05:37:18 +02:00
|
|
|
attributeNamesList := []string{"Eye Color", "Lactose Tolerance", "Height", "Autism", "Homosexualness", "Obesity"}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
// We create the folders for each attribute's training data
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
for _, attributeName := range attributeNamesList{
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
attributeNameWithoutWhitespace := strings.ReplaceAll(attributeName, " ", "")
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
folderpath := goFilepath.Join("./TrainingData/", attributeNameWithoutWhitespace)
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-15 13:02:31 +02:00
|
|
|
_, err = localFilesystem.CreateFolder(folderpath)
|
2024-04-11 15:51:56 +02:00
|
|
|
if (err != nil) { return false, false, err }
|
2024-07-05 23:01:43 +02:00
|
|
|
|
|
|
|
folderExists, err := localFilesystem.DeleteAllFolderContents(folderpath)
|
|
|
|
if (err != nil) { return false, false, err }
|
|
|
|
if (folderExists == false){
|
|
|
|
return false, false, errors.New("CreateFolder failed to create folder.")
|
|
|
|
}
|
2024-06-15 13:02:31 +02:00
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-15 13:02:31 +02:00
|
|
|
numberOfUserPhenotypeDataObjects := len(userPhenotypesList_OpenSNP)
|
|
|
|
maximumIndex := numberOfUserPhenotypeDataObjects-1
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-15 13:02:31 +02:00
|
|
|
numberOfUsersString := helpers.ConvertIntToString(numberOfUserPhenotypeDataObjects)
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-15 13:02:31 +02:00
|
|
|
for index, userPhenotypeDataObject := range userPhenotypesList_OpenSNP{
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
createDataIsStoppedBoolMutex.RLock()
|
|
|
|
createDataIsStopped := createDataIsStoppedBool
|
|
|
|
createDataIsStoppedBoolMutex.RUnlock()
|
|
|
|
|
|
|
|
if (createDataIsStopped == true){
|
|
|
|
// User exited the process
|
|
|
|
return false, false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
userIndexString := helpers.ConvertIntToString(index + 1)
|
|
|
|
|
|
|
|
progressDetailsStatus := "Processing User " + userIndexString + "/" + numberOfUsersString
|
|
|
|
|
|
|
|
err = progressDetailsBinding.Set(progressDetailsStatus)
|
|
|
|
if (err != nil) { return false, false, err }
|
|
|
|
|
2024-08-07 09:45:31 +02:00
|
|
|
trainingProgressPercentage, err := helpers.ScaleIntProportionally(true, index, 0, maximumIndex, 0, 100)
|
2024-06-15 13:02:31 +02:00
|
|
|
if (err != nil) { return false, false, err }
|
|
|
|
|
|
|
|
trainingProgressFloat64 := float64(trainingProgressPercentage)/100
|
|
|
|
|
|
|
|
err = progressPercentageBinding.Set(trainingProgressFloat64)
|
|
|
|
if (err != nil) { return false, false, err }
|
|
|
|
|
|
|
|
processProgressMutex.Lock()
|
|
|
|
processProgress = trainingProgressFloat64
|
|
|
|
processProgressMutex.Unlock()
|
|
|
|
|
2024-04-11 15:51:56 +02:00
|
|
|
userID := userPhenotypeDataObject.UserID
|
|
|
|
|
|
|
|
userRawGenomeFilepathsList, exists := userRawGenomeFilepathsMap[userID]
|
|
|
|
if (exists == false){
|
|
|
|
// User has no genomes
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// We read all of the user's raw genomes and combine them into a single genomeMap which excludes conflicting loci values
|
|
|
|
|
|
|
|
userRawGenomesWithMetadataList := make([]prepareRawGenomes.RawGenomeWithMetadata, 0)
|
|
|
|
|
|
|
|
for _, userRawGenomeFilepath := range userRawGenomeFilepathsList{
|
|
|
|
|
|
|
|
//Outputs:
|
|
|
|
// -bool: Able to read raw genome file
|
|
|
|
// -bool: Genome is phased
|
|
|
|
// -map[int64]readRawGenomes.RawGenomeLocusValue
|
|
|
|
// -error
|
|
|
|
readRawGenomeMap := func()(bool, bool, map[int64]readRawGenomes.RawGenomeLocusValue, error){
|
|
|
|
|
|
|
|
fileObject, err := os.Open(userRawGenomeFilepath)
|
|
|
|
if (err != nil) { return false, false, nil, err }
|
|
|
|
defer fileObject.Close()
|
|
|
|
|
|
|
|
_, _, _, _, genomeIsPhased, rawGenomeMap, err := readRawGenomes.ReadRawGenomeFile(fileObject)
|
|
|
|
if (err != nil) {
|
|
|
|
//log.Println("Raw genome file is malformed: " + userRawGenomeFilepath + ". Reason: " + err.Error())
|
|
|
|
return false, false, nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return true, genomeIsPhased, rawGenomeMap, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
ableToReadRawGenome, rawGenomeIsPhased, rawGenomeMap, err := readRawGenomeMap()
|
|
|
|
if (err != nil){ return false, false, err }
|
|
|
|
if (ableToReadRawGenome == false){
|
|
|
|
// We cannot read this genome file
|
|
|
|
// Many of the genome files are unreadable.
|
|
|
|
//TODO: Improve ability to read slightly corrupted genome files
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-06-02 10:43:39 +02:00
|
|
|
newGenomeIdentifier, err := helpers.GetNewRandom16ByteArray()
|
2024-04-11 15:51:56 +02:00
|
|
|
if (err != nil) { return false, false, err }
|
|
|
|
|
|
|
|
rawGenomeWithMetadata := prepareRawGenomes.RawGenomeWithMetadata{
|
|
|
|
GenomeIdentifier: newGenomeIdentifier,
|
|
|
|
GenomeIsPhased: rawGenomeIsPhased,
|
|
|
|
RawGenomeMap: rawGenomeMap,
|
|
|
|
}
|
|
|
|
|
|
|
|
userRawGenomesWithMetadataList = append(userRawGenomesWithMetadataList, rawGenomeWithMetadata)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (len(userRawGenomesWithMetadataList) == 0){
|
|
|
|
// None of the user's genome files are readable
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-08-07 09:45:31 +02:00
|
|
|
//Outputs:
|
|
|
|
// -bool: Any useful locations exist in any of the user's genomes
|
|
|
|
// -map[int64]locusValue.LocusValue
|
|
|
|
// -error
|
|
|
|
getUserLociValuesMap := func()(bool, map[int64]locusValue.LocusValue, error){
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
updatePercentageCompleteFunction := func(_ int)error{
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-08-07 09:45:31 +02:00
|
|
|
anyUsefulLocationsExist, genomesWithMetadataList, _, combinedGenomesExist, onlyExcludeConflictsGenomeIdentifier, _, err := prepareRawGenomes.GetGenomesWithMetadataListFromRawGenomesList(userRawGenomesWithMetadataList, updatePercentageCompleteFunction)
|
|
|
|
if (err != nil) { return false, nil, err }
|
|
|
|
if (anyUsefulLocationsExist == false){
|
|
|
|
// None of the user's genomes have any useful locations
|
|
|
|
return false, nil, nil
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
if (combinedGenomesExist == false){
|
|
|
|
|
|
|
|
if (len(genomesWithMetadataList) != 1){
|
2024-08-07 09:45:31 +02:00
|
|
|
return false, nil, errors.New("GetGenomesWithMetadataListFromRawGenomesList returning non-1 length genomesWithMetadataList when combinedGenomesExist == false")
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Only 1 genome exists
|
|
|
|
|
|
|
|
genomeWithMetadataObject := genomesWithMetadataList[0]
|
|
|
|
|
|
|
|
genomeMap := genomeWithMetadataObject.GenomeMap
|
|
|
|
|
2024-08-07 09:45:31 +02:00
|
|
|
return true, genomeMap, nil
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, genomeWithMetadataObject := range genomesWithMetadataList{
|
|
|
|
|
|
|
|
genomeIdentifier := genomeWithMetadataObject.GenomeIdentifier
|
|
|
|
|
|
|
|
if (genomeIdentifier == onlyExcludeConflictsGenomeIdentifier){
|
|
|
|
|
|
|
|
genomeMap := genomeWithMetadataObject.GenomeMap
|
|
|
|
|
2024-08-07 09:45:31 +02:00
|
|
|
return true, genomeMap, nil
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-07 09:45:31 +02:00
|
|
|
return false, nil, errors.New("OnlyExcludeConflicts genome not found from GetGenomesWithMetadataListFromRawGenomesList's returned list.")
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-08-07 09:45:31 +02:00
|
|
|
anyUsefulLocationsExist, userLociValuesMap, err := getUserLociValuesMap()
|
2024-04-11 15:51:56 +02:00
|
|
|
if (err != nil) { return false, false, err }
|
2024-08-07 09:45:31 +02:00
|
|
|
if (anyUsefulLocationsExist == false){
|
|
|
|
// None of the user's genome files contain any useful locations
|
|
|
|
continue
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
for _, attributeName := range attributeNamesList{
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
attributeNameWithoutWhitespace := strings.ReplaceAll(attributeName, " ", "")
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
trainingDataFolderpath := goFilepath.Join("./TrainingData", attributeNameWithoutWhitespace)
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
userDataExists, userTrainingDataList, err := geneticPrediction.CreateGeneticPredictionTrainingData_OpenSNP(attributeName, userPhenotypeDataObject, userLociValuesMap)
|
2024-04-11 15:51:56 +02:00
|
|
|
if (err != nil) { return false, false, err }
|
|
|
|
if (userDataExists == false){
|
|
|
|
// User cannot be used for training
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
for index, trainingData := range userTrainingDataList{
|
|
|
|
|
|
|
|
userTrainingDataBytes, err := geneticPrediction.EncodeTrainingDataObjectToBytes(trainingData)
|
|
|
|
if (err != nil) { return false, false, err }
|
|
|
|
|
|
|
|
trainingDataIndexString := helpers.ConvertIntToString(index+1)
|
|
|
|
|
|
|
|
userIDString := helpers.ConvertIntToString(userID)
|
|
|
|
|
|
|
|
trainingDataFilename := "User" + userIDString + "_TrainingData_" + trainingDataIndexString + ".gob"
|
|
|
|
|
|
|
|
err = localFilesystem.CreateOrOverwriteFile(userTrainingDataBytes, trainingDataFolderpath, trainingDataFilename)
|
|
|
|
if (err != nil) { return false, false, err }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-15 13:02:31 +02:00
|
|
|
createDataIsStoppedBoolMutex.Lock()
|
|
|
|
createDataIsStoppedBool = true
|
|
|
|
createDataIsStoppedBoolMutex.Unlock()
|
|
|
|
|
|
|
|
return true, true, nil
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-06-15 13:02:31 +02:00
|
|
|
processIsComplete, archiveIsWellFormed, err := createTrainingData()
|
2024-04-11 15:51:56 +02:00
|
|
|
if (err != nil){
|
2024-06-15 13:02:31 +02:00
|
|
|
createDataIsStoppedBoolMutex.Lock()
|
|
|
|
createDataIsStoppedBool = true
|
|
|
|
createDataIsStoppedBoolMutex.Unlock()
|
|
|
|
|
2024-04-11 15:51:56 +02:00
|
|
|
setErrorEncounteredPage(window, err, previousPage)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (processIsComplete == false){
|
|
|
|
// User exited the page
|
|
|
|
return
|
|
|
|
}
|
2024-06-15 13:02:31 +02:00
|
|
|
if (archiveIsWellFormed == false){
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
title := getBoldLabelCentered("OpenSNP Archive Is Corrupt")
|
|
|
|
|
|
|
|
description1 := getBoldLabelCentered("Your downloaded OpenSNP data archive is corrupt.")
|
|
|
|
description2 := getLabelCentered("The extracted folder contents do not match what the archive should contain.")
|
|
|
|
description3 := getLabelCentered("You should re-extract the contents of the archive.")
|
|
|
|
|
|
|
|
exitButton := getWidgetCentered(widget.NewButtonWithIcon("Exit", theme.CancelIcon(), previousPage))
|
|
|
|
|
|
|
|
page := container.NewVBox(title, widget.NewSeparator(), description1, description2, description3, exitButton)
|
|
|
|
|
|
|
|
window.SetContent(page)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
setCreateTrainingDataIsCompletePage(window)
|
|
|
|
}
|
|
|
|
|
|
|
|
go createTrainingDataFunction()
|
|
|
|
}
|
|
|
|
|
|
|
|
func setCreateTrainingDataIsCompletePage(window fyne.Window){
|
|
|
|
|
|
|
|
title := getBoldLabelCentered("Creating Data Is Complete")
|
|
|
|
|
|
|
|
description1 := getLabelCentered("Creating training data is complete!")
|
|
|
|
description2 := getLabelCentered("The data have been saved in the TrainingData folder.")
|
|
|
|
|
|
|
|
exitButton := getWidgetCentered(widget.NewButtonWithIcon("Exit", theme.CancelIcon(), func(){
|
|
|
|
setHomePage(window)
|
|
|
|
}))
|
|
|
|
|
|
|
|
page := container.NewVBox(title, widget.NewSeparator(), description1, description2, exitButton)
|
|
|
|
|
|
|
|
window.SetContent(page)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func setTrainModelsPage(window fyne.Window, previousPage func()){
|
|
|
|
|
|
|
|
currentPage := func(){setTrainModelsPage(window, previousPage)}
|
|
|
|
|
|
|
|
title := getBoldLabelCentered("Train Models")
|
|
|
|
|
|
|
|
backButton := getBackButtonCentered(previousPage)
|
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
description1 := getLabelCentered("Press the button below to begin training a genetic model.")
|
|
|
|
description2 := getLabelCentered("This will train a neural network using the user training data.")
|
2024-04-11 15:51:56 +02:00
|
|
|
description3 := getLabelCentered("This will take a while.")
|
2024-08-13 15:25:47 +02:00
|
|
|
description4 := getLabelCentered("You must select a model to train.")
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-14 05:37:18 +02:00
|
|
|
attributeNamesList := []string{"Eye Color", "Lactose Tolerance", "Height", "Autism", "Homosexualness", "Obesity"}
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
attributeNameSelector := widget.NewSelect(attributeNamesList, nil)
|
2024-07-05 23:01:43 +02:00
|
|
|
|
|
|
|
beginTrainingButton := getWidgetCentered(widget.NewButtonWithIcon("Begin Training Model", theme.MediaPlayIcon(), func(){
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
selectedAttributeIndex := attributeNameSelector.SelectedIndex()
|
|
|
|
if (selectedAttributeIndex < 0){
|
|
|
|
title := "No Attribute Selected"
|
|
|
|
dialogMessage1 := getLabelCentered("You must select an attribute model to train.")
|
2024-07-05 23:01:43 +02:00
|
|
|
dialogContent := container.NewVBox(dialogMessage1)
|
|
|
|
dialog.ShowCustom(title, "Close", dialogContent, window)
|
|
|
|
return
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
attributeName := attributeNameSelector.Selected
|
|
|
|
setStartAndMonitorTrainModelPage(window, attributeName, currentPage)
|
2024-04-11 15:51:56 +02:00
|
|
|
}))
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
attributeNameSelectorCentered := getWidgetCentered(attributeNameSelector)
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, description4, widget.NewSeparator(), attributeNameSelectorCentered, widget.NewSeparator(), beginTrainingButton)
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
window.SetContent(page)
|
|
|
|
}
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
func setStartAndMonitorTrainModelPage(window fyne.Window, attributeName string, previousPage func()){
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
title := getBoldLabelCentered("Train Model")
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
//TODO: Verify TrainingData folder integrity
|
|
|
|
|
|
|
|
progressDetailsBinding := binding.NewString()
|
2024-06-15 13:02:31 +02:00
|
|
|
estimatedTimeRemainingBinding := binding.NewString()
|
2024-04-11 15:51:56 +02:00
|
|
|
progressPercentageBinding := binding.NewFloat()
|
|
|
|
|
|
|
|
loadingBar := getWidgetCentered(widget.NewProgressBarWithData(progressPercentageBinding))
|
|
|
|
|
|
|
|
progressDetailsTitle := getBoldLabelCentered("Progress Details:")
|
|
|
|
|
|
|
|
progressDetailsLabel := widget.NewLabelWithData(progressDetailsBinding)
|
|
|
|
progressDetailsLabel.TextStyle = fyne.TextStyle{
|
|
|
|
Bold: false,
|
|
|
|
Italic: true,
|
|
|
|
Monospace: false,
|
|
|
|
}
|
|
|
|
|
|
|
|
progressDetailsLabelCentered := getWidgetCentered(progressDetailsLabel)
|
|
|
|
|
2024-06-15 13:02:31 +02:00
|
|
|
estimatedTimeRemainingLabel := widget.NewLabelWithData(estimatedTimeRemainingBinding)
|
|
|
|
estimatedTimeRemainingLabel.TextStyle = fyne.TextStyle{
|
|
|
|
Bold: false,
|
|
|
|
Italic: true,
|
|
|
|
Monospace: false,
|
|
|
|
}
|
|
|
|
|
|
|
|
estimatedTimeRemainingLabelCentered := getWidgetCentered(estimatedTimeRemainingLabel)
|
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
// We set this bool to true to stop the trainModel process
|
|
|
|
var trainModelIsStoppedBoolMutex sync.RWMutex
|
|
|
|
trainModelIsStoppedBool := false
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
cancelButton := getWidgetCentered(widget.NewButtonWithIcon("Cancel", theme.CancelIcon(), func(){
|
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
trainModelIsStoppedBoolMutex.Lock()
|
|
|
|
trainModelIsStoppedBool = true
|
|
|
|
trainModelIsStoppedBoolMutex.Unlock()
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
previousPage()
|
|
|
|
}))
|
|
|
|
|
2024-06-15 13:02:31 +02:00
|
|
|
page := container.NewVBox(title, widget.NewSeparator(), loadingBar, progressDetailsTitle, progressDetailsLabelCentered, estimatedTimeRemainingLabelCentered, widget.NewSeparator(), cancelButton)
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
window.SetContent(page)
|
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
trainModelFunction := func(){
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-06-15 13:02:31 +02:00
|
|
|
var processProgressMutex sync.RWMutex
|
|
|
|
// This stores the amount of progress which has been completed (0-1)
|
|
|
|
processProgress := float64(0)
|
|
|
|
|
|
|
|
startUpdateTimeRemainingDisplayFunction := func(){
|
|
|
|
|
|
|
|
// This function updates the estimated time remaining label binding
|
|
|
|
|
|
|
|
updateTimeRemainingDisplayFunction := func()error{
|
|
|
|
|
|
|
|
startTime := time.Now().Unix()
|
|
|
|
|
|
|
|
for{
|
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
trainModelIsStoppedBoolMutex.RLock()
|
|
|
|
trainModelIsStopped := trainModelIsStoppedBool
|
|
|
|
trainModelIsStoppedBoolMutex.RUnlock()
|
2024-06-15 13:02:31 +02:00
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
if (trainModelIsStopped == true){
|
2024-06-15 13:02:31 +02:00
|
|
|
// User exited the process/Process has completed
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
processProgressMutex.RLock()
|
|
|
|
currentProcessProgress := processProgress
|
|
|
|
processProgressMutex.RUnlock()
|
|
|
|
|
|
|
|
if (currentProcessProgress == 0){
|
|
|
|
estimatedTimeRemainingBinding.Set("Calculating required time...")
|
|
|
|
time.Sleep(time.Second)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// We calculate how long we think it will take for the process to complete
|
|
|
|
|
|
|
|
currentTime := time.Now().Unix()
|
|
|
|
|
|
|
|
secondsElapsed := currentTime - startTime
|
|
|
|
|
|
|
|
// processProgress is a float64 which stores the progress as a value between 0-1
|
|
|
|
// To get the estimated total time the process will take, we divide the seconds elapsed by the proportion of progress
|
|
|
|
// For example:
|
|
|
|
// 0.1 (10%) at 10 seconds == Total process will take 100 seconds
|
|
|
|
// 0.5 (50%) at 20 seconds == Total process will take 40 seconds
|
|
|
|
|
|
|
|
totalSeconds := float64(secondsElapsed)/currentProcessProgress
|
|
|
|
|
|
|
|
estimatedSecondsRemaining := int64(totalSeconds) - secondsElapsed
|
|
|
|
|
|
|
|
estimatedTimeRemainingTranslated, err := helpers.ConvertUnixTimeDurationToUnitsTimeTranslated(estimatedSecondsRemaining, false)
|
|
|
|
if (err != nil) { return err }
|
|
|
|
|
|
|
|
estimatedTimeRemainingBinding.Set("Estimated Time Remaining: " + estimatedTimeRemainingTranslated)
|
|
|
|
|
|
|
|
time.Sleep(time.Second)
|
|
|
|
}
|
|
|
|
|
|
|
|
// This should never be reached
|
|
|
|
|
|
|
|
return errors.New("updateTimeRemainingDisplayFunction loop has broken.")
|
|
|
|
}
|
|
|
|
|
|
|
|
err := updateTimeRemainingDisplayFunction()
|
|
|
|
if (err != nil){
|
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
trainModelIsStoppedBoolMutex.Lock()
|
|
|
|
trainModelIsStoppedBool = true
|
|
|
|
trainModelIsStoppedBoolMutex.Unlock()
|
2024-06-15 13:02:31 +02:00
|
|
|
|
|
|
|
setErrorEncounteredPage(window, err, previousPage)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
go startUpdateTimeRemainingDisplayFunction()
|
|
|
|
|
2024-04-11 15:51:56 +02:00
|
|
|
//Outputs:
|
|
|
|
// -bool: Process completed (true == was not stopped mid-way)
|
|
|
|
// -error
|
2024-07-05 23:01:43 +02:00
|
|
|
trainModel := func()(bool, error){
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
_, err := localFilesystem.CreateFolder("./TrainedModels")
|
|
|
|
if (err != nil) { return false, err }
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
trainingSetFilepathsList, _, err := getTrainingAndTestingDataFilepathLists(attributeName)
|
2024-07-05 23:01:43 +02:00
|
|
|
if (err != nil) { return false, err }
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-07-19 19:16:28 +02:00
|
|
|
// Now we deterministically randomize the order of the trainingSetFilepathsList
|
|
|
|
pseudorandomNumberGenerator := mathRand.New(mathRand.NewPCG(1, 2))
|
|
|
|
|
|
|
|
pseudorandomNumberGenerator.Shuffle(len(trainingSetFilepathsList), func(i int, j int){
|
|
|
|
trainingSetFilepathsList[i], trainingSetFilepathsList[j] = trainingSetFilepathsList[j], trainingSetFilepathsList[i]
|
|
|
|
})
|
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
// We create a new neural network object to train
|
2024-08-13 15:25:47 +02:00
|
|
|
neuralNetworkObject, err := geneticPrediction.GetNewUntrainedNeuralNetworkObject(attributeName)
|
2024-07-05 23:01:43 +02:00
|
|
|
if (err != nil) { return false, err }
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-09 16:23:37 +02:00
|
|
|
// The number of rounds of training for the training data set
|
|
|
|
totalQuantityOfRoundsToRun := 2
|
|
|
|
|
|
|
|
quantityOfTrainingDatasInSet := len(trainingSetFilepathsList)
|
|
|
|
|
|
|
|
quantityOfTrainingDatas := len(trainingSetFilepathsList) * totalQuantityOfRoundsToRun
|
|
|
|
quantityOfTrainingDatasString := helpers.ConvertIntToString(quantityOfTrainingDatas)
|
|
|
|
|
|
|
|
// This keeps track of how many training rounds we have completed
|
|
|
|
// With each round, we shuffle the training data list and train the model again
|
|
|
|
trainingRoundsCompleted := 0
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
// This keeps track of how far along we are in training
|
|
|
|
trainingDataIndex := 0
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-09 16:23:37 +02:00
|
|
|
// This keeps track of how many examples we have trained during all rounds
|
|
|
|
quantityOfExamplesTrained := 0
|
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
// Outputs:
|
|
|
|
// -bool: User stopped training
|
|
|
|
// -bool: Another training data exists
|
|
|
|
// -geneticPrediction.TrainingData
|
|
|
|
// -error
|
|
|
|
getNextTrainingDataFunction := func()(bool, bool, geneticPrediction.TrainingData, error){
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
trainModelIsStoppedBoolMutex.RLock()
|
|
|
|
trainModelIsStopped := trainModelIsStoppedBool
|
|
|
|
trainModelIsStoppedBoolMutex.RUnlock()
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
if (trainModelIsStopped == true){
|
|
|
|
// User exited the process
|
|
|
|
return true, false, geneticPrediction.TrainingData{}, nil
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-09 16:23:37 +02:00
|
|
|
if (trainingDataIndex == quantityOfTrainingDatasInSet){
|
|
|
|
// We are done training this set
|
|
|
|
|
|
|
|
trainingRoundsCompleted += 1
|
|
|
|
|
|
|
|
if (trainingRoundsCompleted == totalQuantityOfRoundsToRun){
|
|
|
|
// We are done training
|
|
|
|
return false, false, geneticPrediction.TrainingData{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// We train another round
|
|
|
|
trainingDataIndex = 0
|
|
|
|
|
|
|
|
// We deterministically randomize the order of the training data for the next round
|
|
|
|
|
|
|
|
pseudorandomNumberGenerator.Shuffle(len(trainingSetFilepathsList), func(i int, j int){
|
|
|
|
trainingSetFilepathsList[i], trainingSetFilepathsList[j] = trainingSetFilepathsList[j], trainingSetFilepathsList[i]
|
|
|
|
})
|
2024-07-05 23:01:43 +02:00
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
trainingDataFilepath := trainingSetFilepathsList[trainingDataIndex]
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
fileExists, fileContents, err := localFilesystem.GetFileContents(trainingDataFilepath)
|
|
|
|
if (err != nil) { return false, false, geneticPrediction.TrainingData{}, err }
|
|
|
|
if (fileExists == false){
|
|
|
|
return false, false, geneticPrediction.TrainingData{}, errors.New("TrainingData file not found: " + trainingDataFilepath)
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
trainingDataObject, err := geneticPrediction.DecodeBytesToTrainingDataObject(fileContents)
|
|
|
|
if (err != nil) { return false, false, geneticPrediction.TrainingData{}, err }
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
trainingDataIndex += 1
|
2024-08-09 16:23:37 +02:00
|
|
|
quantityOfExamplesTrained += 1
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-09 16:23:37 +02:00
|
|
|
quantityOfExamplesTrainedString := helpers.ConvertIntToString(quantityOfExamplesTrained)
|
|
|
|
numberOfExamplesProgress := "Trained " + quantityOfExamplesTrainedString + "/" + quantityOfTrainingDatasString + " Examples"
|
2024-06-15 13:02:31 +02:00
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
progressDetailsBinding.Set(numberOfExamplesProgress)
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-09 16:23:37 +02:00
|
|
|
newProgressFloat64 := float64(quantityOfExamplesTrained)/float64(quantityOfTrainingDatas)
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
err = progressPercentageBinding.Set(newProgressFloat64)
|
|
|
|
if (err != nil) { return false, false, geneticPrediction.TrainingData{}, err }
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
processProgressMutex.Lock()
|
|
|
|
processProgress = newProgressFloat64
|
|
|
|
processProgressMutex.Unlock()
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
return false, true, trainingDataObject, nil
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
getAttributeIsNumericBool := func()(bool, error){
|
2024-08-05 09:11:10 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
switch attributeName{
|
2024-08-05 09:11:10 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
case "Height",
|
|
|
|
"Autism",
|
2024-08-14 05:37:18 +02:00
|
|
|
"Homosexualness",
|
|
|
|
"Obesity":{
|
2024-08-13 15:25:47 +02:00
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
case "Lactose Tolerance",
|
|
|
|
"Eye Color":{
|
|
|
|
return false, nil
|
|
|
|
}
|
2024-08-05 09:11:10 +02:00
|
|
|
}
|
2024-08-13 15:25:47 +02:00
|
|
|
|
|
|
|
return false, errors.New("setStartAndMonitorTrainModelPage called with unknown attributeName: " + attributeName)
|
2024-08-05 09:11:10 +02:00
|
|
|
}
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
attributeIsNumeric, err := getAttributeIsNumericBool()
|
|
|
|
if (err != nil) { return false, err }
|
2024-08-09 16:23:37 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
processCompleted, err := geneticPrediction.TrainNeuralNetwork(attributeName, attributeIsNumeric, neuralNetworkObject, getNextTrainingDataFunction)
|
2024-07-05 23:01:43 +02:00
|
|
|
if (err != nil) { return false, err }
|
|
|
|
if (processCompleted == false){
|
|
|
|
return false, nil
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
// Network training is complete.
|
|
|
|
// We now save the neural network as a .gob file
|
|
|
|
|
2024-08-15 14:14:23 +02:00
|
|
|
neuralNetworkBytes, err := geneticPredictionModels.EncodeNeuralNetworkObjectToBytes(*neuralNetworkObject)
|
2024-07-05 23:01:43 +02:00
|
|
|
if (err != nil) { return false, err }
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
attributeNameWithoutWhitespaces := strings.ReplaceAll(attributeName, " ", "")
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
neuralNetworkFilename := attributeNameWithoutWhitespaces + "Model.gob"
|
2024-07-05 23:01:43 +02:00
|
|
|
|
|
|
|
err = localFilesystem.CreateOrOverwriteFile(neuralNetworkBytes, "./TrainedModels/", neuralNetworkFilename)
|
|
|
|
if (err != nil) { return false, err }
|
|
|
|
|
2024-04-11 15:51:56 +02:00
|
|
|
progressPercentageBinding.Set(1)
|
|
|
|
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
processIsComplete, err := trainModel()
|
2024-04-11 15:51:56 +02:00
|
|
|
if (err != nil){
|
2024-06-15 13:02:31 +02:00
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
trainModelIsStoppedBoolMutex.Lock()
|
|
|
|
trainModelIsStoppedBool = true
|
|
|
|
trainModelIsStoppedBoolMutex.Unlock()
|
2024-06-15 13:02:31 +02:00
|
|
|
|
2024-04-11 15:51:56 +02:00
|
|
|
setErrorEncounteredPage(window, err, previousPage)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (processIsComplete == false){
|
|
|
|
// User exited the page
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
setTrainModelIsCompletePage(window)
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
go trainModelFunction()
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
func setTrainModelIsCompletePage(window fyne.Window){
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
title := getBoldLabelCentered("Training Model Is Complete")
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
description1 := getLabelCentered("Model training is complete!")
|
2024-07-05 23:01:43 +02:00
|
|
|
description2 := getLabelCentered("The model has been saved in the TrainedModels folder.")
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
exitButton := getWidgetCentered(widget.NewButtonWithIcon("Exit", theme.CancelIcon(), func(){
|
|
|
|
setHomePage(window)
|
|
|
|
}))
|
|
|
|
|
|
|
|
page := container.NewVBox(title, widget.NewSeparator(), description1, description2, exitButton)
|
|
|
|
|
|
|
|
window.SetContent(page)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func setTestModelsPage(window fyne.Window, previousPage func()){
|
|
|
|
|
|
|
|
currentPage := func(){setTestModelsPage(window, previousPage)}
|
|
|
|
|
|
|
|
title := getBoldLabelCentered("Test Models")
|
|
|
|
|
|
|
|
backButton := getBackButtonCentered(previousPage)
|
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
description1 := getLabelCentered("Press the button below to begin testing a genetic model.")
|
2024-04-11 15:51:56 +02:00
|
|
|
description2 := getLabelCentered("This will test each neural network using user training data examples.")
|
|
|
|
description3 := getLabelCentered("The testing data is not used to train the models.")
|
|
|
|
description4 := getLabelCentered("The results of the testing will be displayed at the end.")
|
2024-07-19 19:16:28 +02:00
|
|
|
description5 := getLabelCentered("The results will also be saved in the ModelAccuracies folder.")
|
2024-08-13 15:25:47 +02:00
|
|
|
description6 := getLabelCentered("You must select a model to test.")
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-14 05:37:18 +02:00
|
|
|
attributeNamesList := []string{"Eye Color", "Lactose Tolerance", "Height", "Autism", "Homosexualness", "Obesity"}
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
attributeNameSelector := widget.NewSelect(attributeNamesList, nil)
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
beginTestingButton := getWidgetCentered(widget.NewButtonWithIcon("Begin Testing Model", theme.MediaPlayIcon(), func(){
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
selectedAttributeIndex := attributeNameSelector.SelectedIndex()
|
|
|
|
if (selectedAttributeIndex < 0){
|
|
|
|
title := "No Attribute Selected"
|
|
|
|
dialogMessage1 := getLabelCentered("You must select a model to test.")
|
2024-07-05 23:01:43 +02:00
|
|
|
dialogContent := container.NewVBox(dialogMessage1)
|
|
|
|
dialog.ShowCustom(title, "Close", dialogContent, window)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
attributeName := attributeNameSelector.Selected
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
setStartAndMonitorTestModelPage(window, attributeName, currentPage)
|
2024-04-11 15:51:56 +02:00
|
|
|
}))
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
attributeNameSelectorCentered := getWidgetCentered(attributeNameSelector)
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, description3, description4, description5, description6, widget.NewSeparator(), attributeNameSelectorCentered, widget.NewSeparator(), beginTestingButton)
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
window.SetContent(page)
|
|
|
|
}
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
func setStartAndMonitorTestModelPage(window fyne.Window, attributeName string, previousPage func()){
|
2024-07-05 23:01:43 +02:00
|
|
|
|
|
|
|
title := getBoldLabelCentered("Testing Model")
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
progressDetailsBinding := binding.NewString()
|
|
|
|
progressPercentageBinding := binding.NewFloat()
|
|
|
|
|
|
|
|
loadingBar := getWidgetCentered(widget.NewProgressBarWithData(progressPercentageBinding))
|
|
|
|
|
|
|
|
progressDetailsTitle := getBoldLabelCentered("Progress Details:")
|
|
|
|
|
|
|
|
progressDetailsLabel := widget.NewLabelWithData(progressDetailsBinding)
|
|
|
|
progressDetailsLabel.TextStyle = fyne.TextStyle{
|
|
|
|
Bold: false,
|
|
|
|
Italic: true,
|
|
|
|
Monospace: false,
|
|
|
|
}
|
|
|
|
|
|
|
|
progressDetailsLabelCentered := getWidgetCentered(progressDetailsLabel)
|
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
// We set this bool to true to stop the testModel process
|
|
|
|
var testModelIsStoppedBoolMutex sync.RWMutex
|
|
|
|
testModelIsStoppedBool := false
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
cancelButton := getWidgetCentered(widget.NewButtonWithIcon("Cancel", theme.CancelIcon(), func(){
|
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
testModelIsStoppedBoolMutex.Lock()
|
|
|
|
testModelIsStoppedBool = true
|
|
|
|
testModelIsStoppedBoolMutex.Unlock()
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
previousPage()
|
|
|
|
}))
|
|
|
|
|
|
|
|
page := container.NewVBox(title, widget.NewSeparator(), loadingBar, progressDetailsTitle, progressDetailsLabelCentered, widget.NewSeparator(), cancelButton)
|
|
|
|
|
|
|
|
window.SetContent(page)
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
getAttributeIsNumericBool := func()(bool, error){
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
switch attributeName{
|
|
|
|
|
|
|
|
case "Height",
|
|
|
|
"Autism",
|
2024-08-14 05:37:18 +02:00
|
|
|
"Homosexualness",
|
|
|
|
"Obesity":{
|
2024-08-13 15:25:47 +02:00
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
case "Lactose Tolerance",
|
|
|
|
"Eye Color":{
|
|
|
|
return false, nil
|
|
|
|
}
|
2024-08-05 09:11:10 +02:00
|
|
|
}
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
return false, errors.New("setStartAndMonitorTrainModelPage called with unknown attributeName: " + attributeName)
|
|
|
|
}
|
|
|
|
|
|
|
|
attributeIsNumeric, err := getAttributeIsNumericBool()
|
|
|
|
if (err != nil) {
|
|
|
|
setErrorEncounteredPage(window, errors.New("setStartAndMonitorTestModelPage called with unknown attributeName: " + attributeName), previousPage)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (attributeIsNumeric == false){
|
|
|
|
|
|
|
|
// attribute is a Discrete trait
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
testModelFunction := func(){
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
//Outputs:
|
|
|
|
// -bool: Process completed (true == was not stopped mid-way)
|
2024-08-07 09:45:31 +02:00
|
|
|
// -geneticPrediction.DiscreteTraitPredictionAccuracyInfoMap
|
2024-08-05 09:11:10 +02:00
|
|
|
// -error
|
2024-08-15 14:14:23 +02:00
|
|
|
testModel := func()(bool, trainedPredictionModels.DiscreteTraitPredictionAccuracyInfoMap, error){
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
type TraitAccuracyStatisticsValue struct{
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
// This stores the quantity of examples of this outcome
|
|
|
|
QuantityOfExamples int
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
// This stores the quantity of predictions that were made for this outcome
|
|
|
|
// In other words: The quantity of instances where our model predicted this outcome
|
|
|
|
QuantityOfPredictions int
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
// This stores the quantity of predictions that were correct when the genome had this outcome
|
|
|
|
QuantityOfCorrectGenomePredictions int
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
// This stores the quantity of predictions that were correct when the model predicted this outcome
|
|
|
|
QuantityOfCorrectOutcomePredictions int
|
|
|
|
}
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
// We use this map to count up the information about predictions
|
|
|
|
// We use information from this map to construct the final accuracy information map
|
2024-08-15 14:14:23 +02:00
|
|
|
traitPredictionInfoMap := make(map[trainedPredictionModels.DiscreteTraitOutcomeInfo]TraitAccuracyStatisticsValue)
|
2024-07-05 23:01:43 +02:00
|
|
|
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
_, testingSetFilepathsList, err := getTrainingAndTestingDataFilepathLists(attributeName)
|
2024-08-05 09:11:10 +02:00
|
|
|
if (err != nil) { return false, nil, err }
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
traitNameWithoutWhitespaces := strings.ReplaceAll(attributeName, " ", "")
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
// We read the trained model for this trait
|
|
|
|
modelFilename := traitNameWithoutWhitespaces + "Model.gob"
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
trainedModelFilepath := goFilepath.Join("./TrainedModels/", modelFilename)
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
fileExists, fileContents, err := localFilesystem.GetFileContents(trainedModelFilepath)
|
2024-07-05 23:01:43 +02:00
|
|
|
if (err != nil) { return false, nil, err }
|
2024-04-11 15:51:56 +02:00
|
|
|
if (fileExists == false){
|
2024-08-05 09:11:10 +02:00
|
|
|
return false, nil, errors.New("TrainedModel not found: " + trainedModelFilepath)
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-08-15 14:14:23 +02:00
|
|
|
neuralNetworkObject, err := geneticPredictionModels.DecodeBytesToNeuralNetworkObject(fileContents)
|
2024-07-05 23:01:43 +02:00
|
|
|
if (err != nil) { return false, nil, err }
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
numberOfTrainingDatas := len(testingSetFilepathsList)
|
|
|
|
numberOfTrainingDatasString := helpers.ConvertIntToString(numberOfTrainingDatas)
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
finalIndex := numberOfTrainingDatas - 1
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
for index, filePath := range testingSetFilepathsList{
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
testModelIsStoppedBoolMutex.RLock()
|
|
|
|
testModelIsStopped := testModelIsStoppedBool
|
|
|
|
testModelIsStoppedBoolMutex.RUnlock()
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
if (testModelIsStopped == true){
|
|
|
|
// User exited the process
|
|
|
|
return false, nil, nil
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
fileExists, fileContents, err := localFilesystem.GetFileContents(filePath)
|
|
|
|
if (err != nil) { return false, nil, err }
|
|
|
|
if (fileExists == false){
|
|
|
|
return false, nil, errors.New("TrainingData file not found: " + filePath)
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
trainingDataObject, err := geneticPrediction.DecodeBytesToTrainingDataObject(fileContents)
|
|
|
|
if (err != nil) { return false, nil, err }
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
trainingDataInputLayer := trainingDataObject.InputLayer
|
|
|
|
trainingDataExpectedOutputLayer := trainingDataObject.OutputLayer
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
predictionLayer, err := geneticPrediction.GetNeuralNetworkRawPrediction(&neuralNetworkObject, false, trainingDataInputLayer)
|
|
|
|
if (err != nil) { return false, nil, err }
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
numberOfPredictionNeurons := len(predictionLayer)
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
if (len(trainingDataExpectedOutputLayer) != numberOfPredictionNeurons){
|
|
|
|
return false, nil, errors.New("Neural network prediction output length does not match expected output length.")
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
correctOutcomeName, err := geneticPrediction.GetDiscreteOutcomeNameFromOutputLayer(attributeName, true, trainingDataExpectedOutputLayer)
|
2024-08-05 09:11:10 +02:00
|
|
|
if (err != nil) { return false, nil, err }
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
predictedOutcomeName, err := geneticPrediction.GetDiscreteOutcomeNameFromOutputLayer(attributeName, true, predictionLayer)
|
2024-08-05 09:11:10 +02:00
|
|
|
if (err != nil) { return false, nil, err }
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
getPredictionIsCorrectBool := func()bool{
|
|
|
|
if (predictedOutcomeName == correctOutcomeName){
|
|
|
|
return true
|
2024-07-05 23:01:43 +02:00
|
|
|
}
|
2024-08-05 09:11:10 +02:00
|
|
|
return false
|
2024-07-05 23:01:43 +02:00
|
|
|
}
|
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
predictionIsCorrect := getPredictionIsCorrectBool()
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
numberOfKnownLoci, numberOfKnownAndPhasedLoci, numberOfLoci, err := geneticPrediction.GetLociInfoFromNetworkInputLayer(trainingDataInputLayer)
|
|
|
|
if (err != nil) { return false, nil, err }
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
proportionOfLociTested := float64(numberOfKnownLoci)/float64(numberOfLoci)
|
|
|
|
percentageOfLociTested := int(100*proportionOfLociTested)
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
proportionOfPhasedLoci := float64(numberOfKnownAndPhasedLoci)/float64(numberOfKnownLoci)
|
|
|
|
percentageOfPhasedLoci := int(100*proportionOfPhasedLoci)
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
{
|
|
|
|
// We first add the information to the map for the correct outcome
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-15 14:14:23 +02:00
|
|
|
newTraitOutcomeInfo_CorrectOutcome := trainedPredictionModels.DiscreteTraitOutcomeInfo{
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
OutcomeName: correctOutcomeName,
|
|
|
|
PercentageOfLociTested: percentageOfLociTested,
|
|
|
|
PercentageOfPhasedLoci: percentageOfPhasedLoci,
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
getTraitAccuracyStatisticsValue_CorrectOutcome := func()TraitAccuracyStatisticsValue{
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
existingTraitAccuracyStatisticsValue, exists := traitPredictionInfoMap[newTraitOutcomeInfo_CorrectOutcome]
|
|
|
|
if (exists == false){
|
|
|
|
newTraitAccuracyStatisticsValue := TraitAccuracyStatisticsValue{}
|
|
|
|
return newTraitAccuracyStatisticsValue
|
|
|
|
}
|
|
|
|
return existingTraitAccuracyStatisticsValue
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
traitAccuracyStatisticsValue := getTraitAccuracyStatisticsValue_CorrectOutcome()
|
|
|
|
|
|
|
|
traitAccuracyStatisticsValue.QuantityOfExamples += 1
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
if (predictionIsCorrect == true){
|
|
|
|
traitAccuracyStatisticsValue.QuantityOfCorrectGenomePredictions += 1
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
traitPredictionInfoMap[newTraitOutcomeInfo_CorrectOutcome] = traitAccuracyStatisticsValue
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
{
|
|
|
|
// We now add the information to the map for the predicted outcome
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-15 14:14:23 +02:00
|
|
|
newTraitOutcomeInfo_PredictedOutcome := trainedPredictionModels.DiscreteTraitOutcomeInfo{
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
OutcomeName: predictedOutcomeName,
|
|
|
|
PercentageOfLociTested: percentageOfLociTested,
|
|
|
|
PercentageOfPhasedLoci: percentageOfPhasedLoci,
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
getTraitAccuracyStatisticsValue_PredictedOutcome := func()TraitAccuracyStatisticsValue{
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
existingTraitAccuracyStatisticsValue, exists := traitPredictionInfoMap[newTraitOutcomeInfo_PredictedOutcome]
|
|
|
|
if (exists == false){
|
|
|
|
newTraitAccuracyStatisticsValue := TraitAccuracyStatisticsValue{}
|
|
|
|
return newTraitAccuracyStatisticsValue
|
|
|
|
}
|
|
|
|
return existingTraitAccuracyStatisticsValue
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
traitAccuracyStatisticsValue := getTraitAccuracyStatisticsValue_PredictedOutcome()
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
traitAccuracyStatisticsValue.QuantityOfPredictions += 1
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
if (predictionIsCorrect == true){
|
|
|
|
traitAccuracyStatisticsValue.QuantityOfCorrectOutcomePredictions += 1
|
|
|
|
}
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
traitPredictionInfoMap[newTraitOutcomeInfo_PredictedOutcome] = traitAccuracyStatisticsValue
|
|
|
|
}
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
exampleIndexString := helpers.ConvertIntToString(index+1)
|
|
|
|
numberOfExamplesProgress := "Tested " + exampleIndexString + "/" + numberOfTrainingDatasString + " Examples"
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
progressDetailsBinding.Set(numberOfExamplesProgress)
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
newProgressFloat64 := float64(index)/float64(finalIndex)
|
|
|
|
|
|
|
|
progressPercentageBinding.Set(newProgressFloat64)
|
2024-07-05 23:01:43 +02:00
|
|
|
}
|
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
// Now we construct the TraitAccuracyInfoMap
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
// This map stores the accuracy for each outcome
|
2024-08-15 14:14:23 +02:00
|
|
|
traitPredictionAccuracyInfoMap := make(map[trainedPredictionModels.DiscreteTraitOutcomeInfo]trainedPredictionModels.DiscreteTraitPredictionAccuracyInfo)
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-07 09:45:31 +02:00
|
|
|
for traitPredictionInfo, value := range traitPredictionInfoMap{
|
2024-08-05 09:11:10 +02:00
|
|
|
|
|
|
|
quantityOfExamples := value.QuantityOfExamples
|
|
|
|
quantityOfPredictions := value.QuantityOfPredictions
|
|
|
|
|
|
|
|
quantityOfCorrectGenomePredictions := value.QuantityOfCorrectGenomePredictions
|
|
|
|
quantityOfCorrectOutcomePredictions := value.QuantityOfCorrectOutcomePredictions
|
|
|
|
|
|
|
|
if (quantityOfCorrectGenomePredictions > quantityOfExamples){
|
|
|
|
return false, nil, errors.New("traitPredictionInfoMap contains quantityOfCorrectGenomePredictions > quantityOfExamples")
|
|
|
|
}
|
|
|
|
if (quantityOfCorrectOutcomePredictions > quantityOfPredictions){
|
|
|
|
return false, nil, errors.New("traitPredictionInfoMap contains quantityOfCorrectOutcomePredictions > quantityOfPredictions")
|
|
|
|
}
|
|
|
|
|
2024-08-15 14:14:23 +02:00
|
|
|
newTraitPredictionAccuracyInfo := trainedPredictionModels.DiscreteTraitPredictionAccuracyInfo{
|
2024-08-05 09:11:10 +02:00
|
|
|
QuantityOfExamples: quantityOfExamples,
|
|
|
|
QuantityOfPredictions: quantityOfPredictions,
|
|
|
|
}
|
|
|
|
|
|
|
|
if (quantityOfExamples > 0){
|
|
|
|
|
|
|
|
proportionOfCorrectGenomePredictions := float64(quantityOfCorrectGenomePredictions)/float64(quantityOfExamples)
|
|
|
|
percentageOfCorrectGenomePredictions := int(100*proportionOfCorrectGenomePredictions)
|
|
|
|
|
|
|
|
newTraitPredictionAccuracyInfo.ProbabilityOfCorrectGenomePrediction = percentageOfCorrectGenomePredictions
|
|
|
|
}
|
|
|
|
|
|
|
|
if (quantityOfPredictions > 0){
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
proportionOfCorrectOutcomePredictions := float64(quantityOfCorrectOutcomePredictions)/float64(quantityOfPredictions)
|
|
|
|
percentageOfCorrectOutcomePredictions := int(100*proportionOfCorrectOutcomePredictions)
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
newTraitPredictionAccuracyInfo.ProbabilityOfCorrectOutcomePrediction = percentageOfCorrectOutcomePredictions
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-07 09:45:31 +02:00
|
|
|
traitPredictionAccuracyInfoMap[traitPredictionInfo] = newTraitPredictionAccuracyInfo
|
2024-07-05 23:01:43 +02:00
|
|
|
}
|
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
// Testing is complete.
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
// We save the info map as a file in the ModelAccuracies folder
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-15 14:14:23 +02:00
|
|
|
fileBytes, err := trainedPredictionModels.EncodeDiscreteTraitPredictionAccuracyInfoMapToBytes(traitPredictionAccuracyInfoMap)
|
2024-08-05 09:11:10 +02:00
|
|
|
if (err != nil) { return false, nil, err }
|
2024-07-19 19:16:28 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
_, err = localFilesystem.CreateFolder("./ModelAccuracies")
|
|
|
|
if (err != nil) { return false, nil, err }
|
2024-07-19 19:16:28 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
modelAccuracyFilename := traitNameWithoutWhitespaces + "ModelAccuracy.gob"
|
2024-07-19 19:16:28 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
err = localFilesystem.CreateOrOverwriteFile(fileBytes, "./ModelAccuracies/", modelAccuracyFilename)
|
|
|
|
if (err != nil) { return false, nil, err }
|
2024-07-19 19:16:28 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
progressPercentageBinding.Set(1)
|
2024-07-19 19:16:28 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
return true, traitPredictionAccuracyInfoMap, nil
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
processIsComplete, traitPredictionAccuracyInfoMap, err := testModel()
|
|
|
|
if (err != nil){
|
|
|
|
setErrorEncounteredPage(window, err, previousPage)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (processIsComplete == false){
|
|
|
|
// User exited the page
|
|
|
|
return
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
setViewModelTestingDiscreteTraitResultsPage(window, attributeName, traitPredictionAccuracyInfoMap, previousPage)
|
|
|
|
}
|
2024-08-07 09:45:31 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
go testModelFunction()
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
} else {
|
2024-08-07 09:45:31 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
// attribute is Numeric
|
|
|
|
|
|
|
|
testModelFunction := func(){
|
2024-08-07 09:45:31 +02:00
|
|
|
|
|
|
|
//Outputs:
|
|
|
|
// -bool: Process completed (true == was not stopped mid-way)
|
2024-08-13 15:25:47 +02:00
|
|
|
// -geneticPrediction.NumericAttributePredictionAccuracyInfoMap
|
2024-08-07 09:45:31 +02:00
|
|
|
// -error
|
2024-08-15 14:14:23 +02:00
|
|
|
testModel := func()(bool, trainedPredictionModels.NumericAttributePredictionAccuracyInfoMap, error){
|
2024-08-07 09:45:31 +02:00
|
|
|
|
|
|
|
// We use this map to count up the information about predictions
|
|
|
|
// We use information from this map to construct the final accuracy information map
|
2024-08-13 15:25:47 +02:00
|
|
|
// Map Structure: NumericAttributePredictionInfo -> []float64 (List of distances for each prediction)
|
2024-08-15 14:14:23 +02:00
|
|
|
attributePredictionInfoMap := make(map[trainedPredictionModels.NumericAttributePredictionInfo][]float64)
|
2024-08-07 09:45:31 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
_, testingSetFilepathsList, err := getTrainingAndTestingDataFilepathLists(attributeName)
|
2024-08-07 09:45:31 +02:00
|
|
|
if (err != nil) { return false, nil, err }
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
attributeNameWithoutWhitespaces := strings.ReplaceAll(attributeName, " ", "")
|
2024-08-07 09:45:31 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
// We read the trained model for this attribute
|
|
|
|
modelFilename := attributeNameWithoutWhitespaces + "Model.gob"
|
2024-08-07 09:45:31 +02:00
|
|
|
|
|
|
|
trainedModelFilepath := goFilepath.Join("./TrainedModels/", modelFilename)
|
|
|
|
|
|
|
|
fileExists, fileContents, err := localFilesystem.GetFileContents(trainedModelFilepath)
|
|
|
|
if (err != nil) { return false, nil, err }
|
|
|
|
if (fileExists == false){
|
|
|
|
return false, nil, errors.New("TrainedModel not found: " + trainedModelFilepath)
|
|
|
|
}
|
|
|
|
|
2024-08-15 14:14:23 +02:00
|
|
|
neuralNetworkObject, err := geneticPredictionModels.DecodeBytesToNeuralNetworkObject(fileContents)
|
2024-08-07 09:45:31 +02:00
|
|
|
if (err != nil) { return false, nil, err }
|
|
|
|
|
|
|
|
numberOfTrainingDatas := len(testingSetFilepathsList)
|
|
|
|
numberOfTrainingDatasString := helpers.ConvertIntToString(numberOfTrainingDatas)
|
|
|
|
|
|
|
|
finalIndex := numberOfTrainingDatas - 1
|
|
|
|
|
|
|
|
for index, filePath := range testingSetFilepathsList{
|
|
|
|
|
|
|
|
testModelIsStoppedBoolMutex.RLock()
|
|
|
|
testModelIsStopped := testModelIsStoppedBool
|
|
|
|
testModelIsStoppedBoolMutex.RUnlock()
|
|
|
|
|
|
|
|
if (testModelIsStopped == true){
|
|
|
|
// User exited the process
|
|
|
|
return false, nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
fileExists, fileContents, err := localFilesystem.GetFileContents(filePath)
|
|
|
|
if (err != nil) { return false, nil, err }
|
|
|
|
if (fileExists == false){
|
|
|
|
return false, nil, errors.New("TrainingData file not found: " + filePath)
|
|
|
|
}
|
|
|
|
|
|
|
|
trainingDataObject, err := geneticPrediction.DecodeBytesToTrainingDataObject(fileContents)
|
|
|
|
if (err != nil) { return false, nil, err }
|
|
|
|
|
|
|
|
trainingDataInputLayer := trainingDataObject.InputLayer
|
|
|
|
trainingDataExpectedOutputLayer := trainingDataObject.OutputLayer
|
|
|
|
|
2024-08-09 16:23:37 +02:00
|
|
|
if (len(trainingDataExpectedOutputLayer) != 1){
|
|
|
|
return false, nil, errors.New("Neural network training data prediction output layer length is not 1.")
|
|
|
|
}
|
|
|
|
|
|
|
|
predictionLayer, err := geneticPrediction.GetNeuralNetworkRawPrediction(&neuralNetworkObject, true, trainingDataInputLayer)
|
2024-08-07 09:45:31 +02:00
|
|
|
if (err != nil) { return false, nil, err }
|
|
|
|
|
|
|
|
if (len(predictionLayer) != 1){
|
|
|
|
return false, nil, errors.New("Neural network numeric prediction output layer length is not 1.")
|
|
|
|
}
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
correctOutcomeValue, err := geneticPrediction.GetNumericOutcomeValueFromOutputLayer(attributeName, trainingDataExpectedOutputLayer)
|
2024-08-07 09:45:31 +02:00
|
|
|
if (err != nil) { return false, nil, err }
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
predictedOutcomeValue, err := geneticPrediction.GetNumericOutcomeValueFromOutputLayer(attributeName, predictionLayer)
|
2024-08-07 09:45:31 +02:00
|
|
|
if (err != nil) { return false, nil, err }
|
|
|
|
|
|
|
|
numberOfKnownLoci, numberOfKnownAndPhasedLoci, numberOfLoci, err := geneticPrediction.GetLociInfoFromNetworkInputLayer(trainingDataInputLayer)
|
|
|
|
if (err != nil) { return false, nil, err }
|
|
|
|
|
|
|
|
proportionOfLociTested := float64(numberOfKnownLoci)/float64(numberOfLoci)
|
|
|
|
percentageOfLociTested := int(100*proportionOfLociTested)
|
|
|
|
|
|
|
|
proportionOfPhasedLoci := float64(numberOfKnownAndPhasedLoci)/float64(numberOfKnownLoci)
|
|
|
|
percentageOfPhasedLoci := int(100*proportionOfPhasedLoci)
|
|
|
|
|
2024-08-15 14:14:23 +02:00
|
|
|
newNumericAttributePredictionInfo := trainedPredictionModels.NumericAttributePredictionInfo{
|
2024-08-07 09:45:31 +02:00
|
|
|
PercentageOfLociTested: percentageOfLociTested,
|
|
|
|
PercentageOfPhasedLoci: percentageOfPhasedLoci,
|
|
|
|
}
|
|
|
|
|
2024-08-09 16:23:37 +02:00
|
|
|
distanceFromCorrectValue := math.Abs(predictedOutcomeValue - correctOutcomeValue)
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
existingList, exists := attributePredictionInfoMap[newNumericAttributePredictionInfo]
|
2024-08-07 09:45:31 +02:00
|
|
|
if (exists == false){
|
2024-08-13 15:25:47 +02:00
|
|
|
attributePredictionInfoMap[newNumericAttributePredictionInfo] = []float64{distanceFromCorrectValue}
|
2024-08-07 09:45:31 +02:00
|
|
|
} else {
|
2024-08-09 16:23:37 +02:00
|
|
|
existingList = append(existingList, distanceFromCorrectValue)
|
2024-08-13 15:25:47 +02:00
|
|
|
attributePredictionInfoMap[newNumericAttributePredictionInfo] = existingList
|
2024-08-07 09:45:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
exampleIndexString := helpers.ConvertIntToString(index+1)
|
|
|
|
numberOfExamplesProgress := "Tested " + exampleIndexString + "/" + numberOfTrainingDatasString + " Examples"
|
|
|
|
|
|
|
|
progressDetailsBinding.Set(numberOfExamplesProgress)
|
|
|
|
|
|
|
|
newProgressFloat64 := float64(index)/float64(finalIndex)
|
|
|
|
|
|
|
|
progressPercentageBinding.Set(newProgressFloat64)
|
|
|
|
}
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
// Now we construct the AttributeAccuracyInfoMap
|
2024-08-07 09:45:31 +02:00
|
|
|
|
2024-08-09 16:23:37 +02:00
|
|
|
// This map stores the accuracy for each QuantityOfKnownLoci/QuantityOfPhasedLoci
|
2024-08-15 14:14:23 +02:00
|
|
|
attributePredictionAccuracyInfoMap := make(map[trainedPredictionModels.NumericAttributePredictionInfo]trainedPredictionModels.NumericAttributePredictionAccuracyRangesMap)
|
2024-08-07 09:45:31 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
for attributePredictionInfo, predictionDistancesList := range attributePredictionInfoMap{
|
2024-08-07 09:45:31 +02:00
|
|
|
|
2024-08-09 16:23:37 +02:00
|
|
|
if (len(predictionDistancesList) == 0){
|
2024-08-13 15:25:47 +02:00
|
|
|
return false, nil, errors.New("attributePredictionInfoMap contains empty predictionDistancesList.")
|
2024-08-07 09:45:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Map Structure: Accuracy Percentage (AP) -> Amount needed to deviate from prediction
|
|
|
|
// for the value to be accurate (AP)% of the time
|
2024-08-13 15:25:47 +02:00
|
|
|
newNumericAttributePredictionAccuracyRangesMap := make(map[int]float64)
|
2024-08-07 09:45:31 +02:00
|
|
|
|
2024-08-09 16:23:37 +02:00
|
|
|
if (len(predictionDistancesList) < 5){
|
|
|
|
// We don't have enough data to create an accuracyRanges map.
|
|
|
|
continue
|
|
|
|
}
|
2024-08-07 09:45:31 +02:00
|
|
|
|
2024-08-09 16:23:37 +02:00
|
|
|
// We sort the prediction distances list in ascending order
|
|
|
|
slices.Sort(predictionDistancesList)
|
2024-08-07 09:45:31 +02:00
|
|
|
|
2024-08-09 16:23:37 +02:00
|
|
|
finalIndex := len(predictionDistancesList) - 1
|
2024-08-07 09:45:31 +02:00
|
|
|
|
2024-08-09 16:23:37 +02:00
|
|
|
for index, distance := range predictionDistancesList{
|
2024-08-07 09:45:31 +02:00
|
|
|
|
2024-08-09 16:23:37 +02:00
|
|
|
proportionOfPredictionsWithinDistance := float64(index)/float64(finalIndex)
|
2024-08-07 09:45:31 +02:00
|
|
|
|
2024-08-09 16:23:37 +02:00
|
|
|
percentageOfPredictionsWithinDistance := int(100 * proportionOfPredictionsWithinDistance)
|
2024-08-07 09:45:31 +02:00
|
|
|
|
2024-08-09 16:23:37 +02:00
|
|
|
if (percentageOfPredictionsWithinDistance == 0){
|
|
|
|
// 0% accuracy is not a useful metric for users
|
|
|
|
continue
|
2024-08-07 09:45:31 +02:00
|
|
|
}
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
_, exists := newNumericAttributePredictionAccuracyRangesMap[percentageOfPredictionsWithinDistance]
|
2024-08-09 16:23:37 +02:00
|
|
|
if (exists == true){
|
|
|
|
// There exists a value for this percentage already
|
|
|
|
// This happens because we convert a float64 to an int
|
|
|
|
// The existing percentage must be smaller than our current percentage
|
|
|
|
// We want to keep that smaller percentage
|
|
|
|
// For example, we would rather keep the 15.1% value than the 15.8% value.
|
|
|
|
continue
|
2024-08-07 09:45:31 +02:00
|
|
|
}
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
newNumericAttributePredictionAccuracyRangesMap[percentageOfPredictionsWithinDistance] = distance
|
2024-08-07 09:45:31 +02:00
|
|
|
}
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
attributePredictionAccuracyInfoMap[attributePredictionInfo] = newNumericAttributePredictionAccuracyRangesMap
|
2024-08-07 09:45:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Testing is complete.
|
|
|
|
|
|
|
|
// We save the info map as a file in the ModelAccuracies folder
|
|
|
|
|
2024-08-15 14:14:23 +02:00
|
|
|
fileBytes, err := trainedPredictionModels.EncodeNumericAttributePredictionAccuracyInfoMapToBytes(attributePredictionAccuracyInfoMap)
|
2024-08-07 09:45:31 +02:00
|
|
|
if (err != nil) { return false, nil, err }
|
|
|
|
|
|
|
|
_, err = localFilesystem.CreateFolder("./ModelAccuracies")
|
|
|
|
if (err != nil) { return false, nil, err }
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
modelAccuracyFilename := attributeNameWithoutWhitespaces + "ModelAccuracy.gob"
|
2024-08-07 09:45:31 +02:00
|
|
|
|
|
|
|
err = localFilesystem.CreateOrOverwriteFile(fileBytes, "./ModelAccuracies/", modelAccuracyFilename)
|
|
|
|
if (err != nil) { return false, nil, err }
|
|
|
|
|
|
|
|
progressPercentageBinding.Set(1)
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
return true, attributePredictionAccuracyInfoMap, nil
|
2024-08-07 09:45:31 +02:00
|
|
|
}
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
processIsComplete, attributePredictionAccuracyInfoMap, err := testModel()
|
2024-08-07 09:45:31 +02:00
|
|
|
if (err != nil){
|
|
|
|
setErrorEncounteredPage(window, err, previousPage)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (processIsComplete == false){
|
|
|
|
// User exited the page
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
setViewModelTestingNumericAttributeResultsPage(window, attributeName, attributePredictionAccuracyInfoMap, previousPage)
|
|
|
|
return
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
go testModelFunction()
|
|
|
|
}
|
2024-07-05 23:01:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// This is a page to view the details of testing for a specific trait's model
|
2024-08-15 14:14:23 +02:00
|
|
|
func setViewModelTestingDiscreteTraitResultsPage(window fyne.Window, traitName string, traitAccuracyInfoMap trainedPredictionModels.DiscreteTraitPredictionAccuracyInfoMap, exitPage func()){
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-05 09:11:10 +02:00
|
|
|
title := getBoldLabelCentered("Discrete Trait Prediction Accuracy Details")
|
2024-07-05 23:01:43 +02:00
|
|
|
|
|
|
|
exitButton := getWidgetCentered(widget.NewButtonWithIcon("Exit", theme.CancelIcon(), exitPage))
|
|
|
|
|
|
|
|
description1 := getLabelCentered("The results of the prediction accuracy for this trait are below.")
|
|
|
|
|
|
|
|
traitNameTitle := widget.NewLabel("Trait Name:")
|
|
|
|
traitNameLabel := getBoldLabel(traitName)
|
|
|
|
traitNameRow := container.NewHBox(layout.NewSpacer(), traitNameTitle, traitNameLabel, layout.NewSpacer())
|
|
|
|
|
|
|
|
description2 := getLabelCentered("Prediction accuracy values are a pair of Genome Accuracy/Outcome Accuracy.")
|
|
|
|
description3 := getLabelCentered("Genome Accuracy is the probability that the model will predict a genome's trait value correctly.")
|
|
|
|
description4 := getLabelCentered("Outcome Accuracy is the probability that a trait prediction that the model makes is correct.")
|
|
|
|
|
|
|
|
getResultsGrid := func()(*fyne.Container, error){
|
|
|
|
|
|
|
|
emptyLabel1 := widget.NewLabel("")
|
2024-08-09 16:23:37 +02:00
|
|
|
outcomeNameTitle := getItalicLabelCentered("Outcome Name")
|
2024-07-05 23:01:43 +02:00
|
|
|
|
|
|
|
predictionAccuracyTitle1 := getItalicLabelCentered("Prediction Accuracy")
|
|
|
|
knownLociLabel_0to33 := getItalicLabelCentered("0-33% Known Loci")
|
|
|
|
|
|
|
|
predictionAccuracyTitle2 := getItalicLabelCentered("Prediction Accuracy")
|
|
|
|
knownLociLabel_34to66 := getItalicLabelCentered("34-66% Known Loci")
|
|
|
|
|
|
|
|
predictionAccuracyTitle3 := getItalicLabelCentered("Prediction Accuracy")
|
|
|
|
knownLociLabel_67to100 := getItalicLabelCentered("67-100% Known Loci")
|
|
|
|
|
2024-08-09 16:23:37 +02:00
|
|
|
outcomeNameColumn := container.NewVBox(emptyLabel1, outcomeNameTitle, widget.NewSeparator())
|
2024-07-05 23:01:43 +02:00
|
|
|
predictionAccuracyColumn_0to33 := container.NewVBox(predictionAccuracyTitle1, knownLociLabel_0to33, widget.NewSeparator())
|
|
|
|
predictionAccuracyColumn_34to66 := container.NewVBox(predictionAccuracyTitle2, knownLociLabel_34to66, widget.NewSeparator())
|
|
|
|
predictionAccuracyColumn_67to100 := container.NewVBox(predictionAccuracyTitle3, knownLociLabel_67to100, widget.NewSeparator())
|
|
|
|
|
|
|
|
traitObject, err := traits.GetTraitObject(traitName)
|
|
|
|
if (err != nil) { return nil, err }
|
|
|
|
|
2024-08-07 09:45:31 +02:00
|
|
|
traitIsDiscreteOrNumeric := traitObject.DiscreteOrNumeric
|
|
|
|
if (traitIsDiscreteOrNumeric != "Discrete"){
|
|
|
|
return nil, errors.New("setViewModelTestingDiscreteTraitResultsPage called with non-discrete trait: " + traitName)
|
|
|
|
}
|
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
outcomeNamesList := traitObject.OutcomesList
|
|
|
|
|
|
|
|
for _, outcomeName := range outcomeNamesList{
|
|
|
|
|
|
|
|
outcomeNameLabel := getBoldLabelCentered(outcomeName)
|
|
|
|
|
|
|
|
// We use the below variables to sum up the accuracy percentages so we can average them
|
|
|
|
|
|
|
|
genomePredictionAccuracySum_0to33 := 0
|
|
|
|
genomeExampleCount_0to33 := 0
|
|
|
|
|
|
|
|
outcomePredictionAccuracySum_0to33 := 0
|
|
|
|
outcomePredictionCount_0to33 := 0
|
|
|
|
|
|
|
|
genomePredictionAccuracySum_34to66 := 0
|
|
|
|
genomeExampleCount_34to66 := 0
|
|
|
|
|
|
|
|
outcomePredictionAccuracySum_34to66 := 0
|
|
|
|
outcomePredictionCount_34to66 := 0
|
|
|
|
|
|
|
|
genomePredictionAccuracySum_67to100 := 0
|
|
|
|
genomeExampleCount_67to100 := 0
|
|
|
|
|
|
|
|
outcomePredictionAccuracySum_67to100 := 0
|
|
|
|
outcomePredictionCount_67to100 := 0
|
|
|
|
|
|
|
|
for traitOutcomeInfo, traitPredictionAccuracyInfo := range traitAccuracyInfoMap{
|
|
|
|
|
|
|
|
currentOutcomeName := traitOutcomeInfo.OutcomeName
|
|
|
|
if (currentOutcomeName != outcomeName){
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
percentageOfLociTested := traitOutcomeInfo.PercentageOfLociTested
|
|
|
|
|
|
|
|
quantityOfExamples := traitPredictionAccuracyInfo.QuantityOfExamples
|
|
|
|
quantityOfPredictions := traitPredictionAccuracyInfo.QuantityOfPredictions
|
|
|
|
|
|
|
|
genomePredictionAccuracyPercentage := traitPredictionAccuracyInfo.ProbabilityOfCorrectGenomePrediction
|
|
|
|
outcomePredictionAccuracyPercentage := traitPredictionAccuracyInfo.ProbabilityOfCorrectOutcomePrediction
|
|
|
|
|
|
|
|
if (percentageOfLociTested <= 33){
|
|
|
|
|
|
|
|
genomePredictionAccuracySum_0to33 += (genomePredictionAccuracyPercentage * quantityOfExamples)
|
|
|
|
genomeExampleCount_0to33 += quantityOfExamples
|
|
|
|
|
|
|
|
outcomePredictionAccuracySum_0to33 += (outcomePredictionAccuracyPercentage * quantityOfPredictions)
|
|
|
|
outcomePredictionCount_0to33 += quantityOfPredictions
|
|
|
|
|
|
|
|
} else if (percentageOfLociTested > 33 && percentageOfLociTested <= 66){
|
|
|
|
|
|
|
|
genomePredictionAccuracySum_34to66 += (genomePredictionAccuracyPercentage * quantityOfExamples)
|
|
|
|
genomeExampleCount_34to66 += quantityOfExamples
|
|
|
|
|
|
|
|
outcomePredictionAccuracySum_34to66 += (outcomePredictionAccuracyPercentage * quantityOfPredictions)
|
|
|
|
outcomePredictionCount_34to66 += quantityOfPredictions
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
genomePredictionAccuracySum_67to100 += (genomePredictionAccuracyPercentage * quantityOfExamples)
|
|
|
|
genomeExampleCount_67to100 += quantityOfExamples
|
|
|
|
|
|
|
|
outcomePredictionAccuracySum_67to100 += (outcomePredictionAccuracyPercentage * quantityOfPredictions)
|
|
|
|
outcomePredictionCount_67to100 += quantityOfPredictions
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
getAverageAccuracyText := func(accuracySum int, predictionCount int)string{
|
|
|
|
if (predictionCount == 0){
|
|
|
|
return "Unknown"
|
|
|
|
}
|
|
|
|
|
|
|
|
averageAccuracy := accuracySum/predictionCount
|
|
|
|
|
|
|
|
averageAccuracyString := helpers.ConvertIntToString(averageAccuracy)
|
|
|
|
|
|
|
|
result := averageAccuracyString + "%"
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
genomeAverageAccuracyText_0to33 := getAverageAccuracyText(genomePredictionAccuracySum_0to33, genomeExampleCount_0to33)
|
|
|
|
genomeAverageAccuracyText_34to66 := getAverageAccuracyText(genomePredictionAccuracySum_34to66, genomeExampleCount_34to66)
|
|
|
|
genomeAverageAccuracyText_67to100 := getAverageAccuracyText(genomePredictionAccuracySum_67to100, genomeExampleCount_67to100)
|
|
|
|
|
|
|
|
outcomeAverageAccuracyText_0to33 := getAverageAccuracyText(outcomePredictionAccuracySum_0to33, outcomePredictionCount_0to33)
|
|
|
|
outcomeAverageAccuracyText_34to66 := getAverageAccuracyText(outcomePredictionAccuracySum_34to66, outcomePredictionCount_34to66)
|
|
|
|
outcomeAverageAccuracyText_67to100 := getAverageAccuracyText(outcomePredictionAccuracySum_67to100, outcomePredictionCount_67to100)
|
|
|
|
|
|
|
|
averageAccuracyLabel_0to33 := getBoldLabelCentered(genomeAverageAccuracyText_0to33 + "/" + outcomeAverageAccuracyText_0to33)
|
|
|
|
averageAccuracyLabel_34to66 := getBoldLabelCentered(genomeAverageAccuracyText_34to66 + "/" + outcomeAverageAccuracyText_34to66)
|
|
|
|
averageAccuracyLabel_67to100 := getBoldLabelCentered(genomeAverageAccuracyText_67to100 + "/" + outcomeAverageAccuracyText_67to100)
|
|
|
|
|
|
|
|
outcomeNameColumn.Add(outcomeNameLabel)
|
|
|
|
predictionAccuracyColumn_0to33.Add(averageAccuracyLabel_0to33)
|
|
|
|
predictionAccuracyColumn_34to66.Add(averageAccuracyLabel_34to66)
|
|
|
|
predictionAccuracyColumn_67to100.Add(averageAccuracyLabel_67to100)
|
|
|
|
|
|
|
|
outcomeNameColumn.Add(widget.NewSeparator())
|
|
|
|
predictionAccuracyColumn_0to33.Add(widget.NewSeparator())
|
|
|
|
predictionAccuracyColumn_34to66.Add(widget.NewSeparator())
|
|
|
|
predictionAccuracyColumn_67to100.Add(widget.NewSeparator())
|
|
|
|
}
|
|
|
|
|
2024-07-19 19:16:28 +02:00
|
|
|
resultsGrid := container.NewHBox(layout.NewSpacer(), outcomeNameColumn, predictionAccuracyColumn_0to33, predictionAccuracyColumn_34to66, predictionAccuracyColumn_67to100, layout.NewSpacer())
|
2024-07-05 23:01:43 +02:00
|
|
|
|
|
|
|
return resultsGrid, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
resultsGrid, err := getResultsGrid()
|
|
|
|
if (err != nil){
|
|
|
|
setErrorEncounteredPage(window, err, func(){setHomePage(window)})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
page := container.NewVBox(title, exitButton, widget.NewSeparator(), description1, widget.NewSeparator(), traitNameRow, widget.NewSeparator(), description2, description3, description4, widget.NewSeparator(), resultsGrid)
|
|
|
|
|
|
|
|
window.SetContent(page)
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
// This is a page to view the details of testing for a numeric attribute's model
|
2024-08-15 14:14:23 +02:00
|
|
|
func setViewModelTestingNumericAttributeResultsPage(window fyne.Window, attributeName string, attributeAccuracyInfoMap trainedPredictionModels.NumericAttributePredictionAccuracyInfoMap, exitPage func()){
|
2024-08-07 09:45:31 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
title := getBoldLabelCentered("Numeric Attribute Prediction Accuracy Details")
|
2024-08-07 09:45:31 +02:00
|
|
|
|
|
|
|
exitButton := getWidgetCentered(widget.NewButtonWithIcon("Exit", theme.CancelIcon(), exitPage))
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
description1 := getLabelCentered("The results of the prediction accuracy for this attribute are below.")
|
2024-08-07 09:45:31 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
attributeNameTitle := widget.NewLabel("Attribute Name:")
|
|
|
|
attributeNameLabel := getBoldLabel(attributeName)
|
|
|
|
attributeNameRow := container.NewHBox(layout.NewSpacer(), attributeNameTitle, attributeNameLabel, layout.NewSpacer())
|
2024-08-07 09:45:31 +02:00
|
|
|
|
|
|
|
description2 := getLabelCentered("Each value is a range that the prediction must be widened by to be accurate X% of the time.")
|
|
|
|
description3 := getLabelCentered("For example, for a height prediction to be accurate 90% of the time, allow a +/-10 cm range.")
|
|
|
|
|
|
|
|
getResultsGrid := func()(*fyne.Container, error){
|
|
|
|
|
|
|
|
probabilityOfTitle := getItalicLabelCentered("Probability Of")
|
|
|
|
correctPredictionTitle := getItalicLabelCentered("Correct Prediction")
|
|
|
|
|
|
|
|
accuracyRangeTitle1 := getItalicLabelCentered("Accuracy Range")
|
|
|
|
knownLociLabel_0to33 := getItalicLabelCentered("0-33% Known Loci")
|
|
|
|
|
|
|
|
accuracyRangeTitle2 := getItalicLabelCentered("Accuracy Range")
|
|
|
|
knownLociLabel_34to66 := getItalicLabelCentered("34-66% Known Loci")
|
|
|
|
|
|
|
|
accuracyRangeTitle3 := getItalicLabelCentered("Accuracy Range")
|
|
|
|
knownLociLabel_67to100 := getItalicLabelCentered("67-100% Known Loci")
|
|
|
|
|
|
|
|
probabilityOfCorrectPredictionColumn := container.NewVBox(probabilityOfTitle, correctPredictionTitle, widget.NewSeparator())
|
|
|
|
accuracyRangeColumn_0to33 := container.NewVBox(accuracyRangeTitle1, knownLociLabel_0to33, widget.NewSeparator())
|
|
|
|
accuracyRangeColumn_34to66 := container.NewVBox(accuracyRangeTitle2, knownLociLabel_34to66, widget.NewSeparator())
|
|
|
|
accuracyRangeColumn_67to100 := container.NewVBox(accuracyRangeTitle3, knownLociLabel_67to100, widget.NewSeparator())
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
// We get the formatter for the distance values
|
|
|
|
// This converts raw predictions to formatted values
|
|
|
|
// Example: 100 -> "100 centimeters"
|
2024-08-07 09:45:31 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
getAttributeValueFormatter := func()(func(float64, bool)(string, error), error){
|
|
|
|
switch attributeName{
|
|
|
|
case "Homosexuality",
|
|
|
|
"Height":{
|
|
|
|
|
|
|
|
traitObject, err := traits.GetTraitObject(attributeName)
|
|
|
|
if (err != nil) { return nil, err }
|
|
|
|
|
|
|
|
numericValueFormatter := traitObject.NumericValueFormatter
|
|
|
|
|
|
|
|
return numericValueFormatter, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// attribute is a polygenic disease
|
|
|
|
|
|
|
|
result := func(inputValue float64, _ bool)(string, error){
|
|
|
|
|
|
|
|
// Input value is a value between 0 and 10
|
|
|
|
|
|
|
|
inputValueFormatted := helpers.ConvertIntToString(int(inputValue))
|
|
|
|
|
|
|
|
return inputValueFormatted, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
2024-08-07 09:45:31 +02:00
|
|
|
}
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
attributeValueFormatter, err := getAttributeValueFormatter()
|
|
|
|
if (err != nil){ return nil, err }
|
|
|
|
|
2024-08-07 09:45:31 +02:00
|
|
|
probabilityMinimumRange := 1
|
|
|
|
|
|
|
|
for {
|
|
|
|
|
|
|
|
if (probabilityMinimumRange == 100){
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
getProbabilityMaximumRange := func()int{
|
|
|
|
|
|
|
|
if (probabilityMinimumRange == 90){
|
|
|
|
return 100
|
|
|
|
}
|
|
|
|
|
|
|
|
probabilityMaximumRange := probabilityMinimumRange + 9
|
|
|
|
|
|
|
|
return probabilityMaximumRange
|
|
|
|
}
|
|
|
|
|
|
|
|
probabilityMaximumRange := getProbabilityMaximumRange()
|
|
|
|
|
|
|
|
probabilityMinimumRangeString := helpers.ConvertIntToString(probabilityMinimumRange)
|
|
|
|
probabilityMaximumRangeString := helpers.ConvertIntToString(probabilityMaximumRange)
|
|
|
|
|
|
|
|
probabilityOfCorrectPredictionRangeFormatted := probabilityMinimumRangeString + "% - " + probabilityMaximumRangeString + "%"
|
|
|
|
|
|
|
|
probabilityOfCorrectPredictionRangeLabel := getBoldLabelCentered(probabilityOfCorrectPredictionRangeFormatted)
|
|
|
|
|
|
|
|
// We use the below variables to sum up the accuracy distances so we can average them
|
|
|
|
|
|
|
|
predictionAccuracyDistancesSum_0to33 := float64(0)
|
|
|
|
distancesCount_0to33 := 0
|
|
|
|
|
|
|
|
predictionAccuracyDistancesSum_34to66 := float64(0)
|
|
|
|
distancesCount_34to66 := 0
|
|
|
|
|
|
|
|
predictionAccuracyDistancesSum_67to100 := float64(0)
|
|
|
|
distancesCount_67to100 := 0
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
for attributeOutcomeInfo, attributePredictionAccuracyRangesMap := range attributeAccuracyInfoMap{
|
2024-08-07 09:45:31 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
percentageOfLociTested := attributeOutcomeInfo.PercentageOfLociTested
|
2024-08-07 09:45:31 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
for percentageCorrect, distance := range attributePredictionAccuracyRangesMap{
|
2024-08-07 09:45:31 +02:00
|
|
|
|
|
|
|
if (percentageCorrect < probabilityMinimumRange || percentageCorrect > probabilityMaximumRange){
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if (percentageOfLociTested <= 33){
|
|
|
|
|
|
|
|
predictionAccuracyDistancesSum_0to33 += distance
|
|
|
|
distancesCount_0to33 += 1
|
|
|
|
|
|
|
|
} else if (percentageOfLociTested > 33 && percentageOfLociTested <= 66){
|
|
|
|
|
|
|
|
predictionAccuracyDistancesSum_34to66 += distance
|
|
|
|
distancesCount_34to66 += 1
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
predictionAccuracyDistancesSum_67to100 += distance
|
|
|
|
distancesCount_67to100 += 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
getAverageAccuracyText := func(distancesSum float64, distancesCount int)(string, error){
|
2024-08-07 09:45:31 +02:00
|
|
|
if (distancesCount == 0){
|
2024-08-13 15:25:47 +02:00
|
|
|
return "Unknown", nil
|
2024-08-07 09:45:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
averageDistance := distancesSum/float64(distancesCount)
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
averageDistanceFormatted, err := attributeValueFormatter(averageDistance, false)
|
|
|
|
if (err != nil) { return "", err }
|
2024-08-07 09:45:31 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
result := "+/- " + averageDistanceFormatted
|
2024-08-07 09:45:31 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
return result, nil
|
2024-08-07 09:45:31 +02:00
|
|
|
}
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
averageDistanceText_0to33, err := getAverageAccuracyText(predictionAccuracyDistancesSum_0to33, distancesCount_0to33)
|
|
|
|
if (err != nil){ return nil, err }
|
|
|
|
averageDistanceText_34to66, err := getAverageAccuracyText(predictionAccuracyDistancesSum_34to66, distancesCount_34to66)
|
|
|
|
if (err != nil){ return nil, err }
|
|
|
|
averageDistanceText_67to100, err := getAverageAccuracyText(predictionAccuracyDistancesSum_67to100, distancesCount_67to100)
|
|
|
|
if (err != nil){ return nil, err }
|
2024-08-07 09:45:31 +02:00
|
|
|
|
|
|
|
averageDistanceLabel_0to33 := getBoldLabelCentered(averageDistanceText_0to33)
|
|
|
|
averageDistanceLabel_34to66 := getBoldLabelCentered(averageDistanceText_34to66)
|
|
|
|
averageDistanceLabel_67to100 := getBoldLabelCentered(averageDistanceText_67to100)
|
|
|
|
|
|
|
|
probabilityOfCorrectPredictionColumn.Add(probabilityOfCorrectPredictionRangeLabel)
|
|
|
|
accuracyRangeColumn_0to33.Add(averageDistanceLabel_0to33)
|
|
|
|
accuracyRangeColumn_34to66.Add(averageDistanceLabel_34to66)
|
|
|
|
accuracyRangeColumn_67to100.Add(averageDistanceLabel_67to100)
|
|
|
|
|
|
|
|
probabilityOfCorrectPredictionColumn.Add(widget.NewSeparator())
|
|
|
|
accuracyRangeColumn_0to33.Add(widget.NewSeparator())
|
|
|
|
accuracyRangeColumn_34to66.Add(widget.NewSeparator())
|
|
|
|
accuracyRangeColumn_67to100.Add(widget.NewSeparator())
|
|
|
|
|
|
|
|
if (probabilityMinimumRange == 1){
|
|
|
|
probabilityMinimumRange = 10
|
|
|
|
} else {
|
|
|
|
probabilityMinimumRange += 10
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
resultsGrid := container.NewHBox(layout.NewSpacer(), probabilityOfCorrectPredictionColumn, accuracyRangeColumn_0to33, accuracyRangeColumn_34to66, accuracyRangeColumn_67to100, layout.NewSpacer())
|
|
|
|
|
|
|
|
return resultsGrid, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
resultsGrid, err := getResultsGrid()
|
|
|
|
if (err != nil){
|
|
|
|
setErrorEncounteredPage(window, err, func(){setHomePage(window)})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
page := container.NewVBox(title, exitButton, widget.NewSeparator(), description1, widget.NewSeparator(), attributeNameRow, widget.NewSeparator(), description2, description3, widget.NewSeparator(), resultsGrid)
|
2024-08-07 09:45:31 +02:00
|
|
|
|
|
|
|
pageScrollable := container.NewVScroll(page)
|
|
|
|
|
|
|
|
window.SetContent(pageScrollable)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
// This function returns a list of training data and testing data filepaths for an attribute.
|
2024-04-11 15:51:56 +02:00
|
|
|
//Outputs:
|
|
|
|
// -[]string: Sorted list of training data filepaths
|
|
|
|
// -[]string: Unsorted list of testing data filepaths
|
|
|
|
// -error
|
2024-08-13 15:25:47 +02:00
|
|
|
func getTrainingAndTestingDataFilepathLists(attributeName string)([]string, []string, error){
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
attributeNameWithoutWhitespaces := strings.ReplaceAll(attributeName, " ", "")
|
2024-04-11 15:51:56 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
trainingDataFolderpath := goFilepath.Join("./TrainingData/", attributeNameWithoutWhitespaces)
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
filesList, err := os.ReadDir(trainingDataFolderpath)
|
|
|
|
if (err != nil) { return nil, nil, err }
|
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
// This map stores the file name for each training data
|
|
|
|
trainingDataFilenamesMap := make(map[string]struct{})
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
for _, filesystemObject := range filesList{
|
|
|
|
|
|
|
|
filepathIsFolder := filesystemObject.IsDir()
|
|
|
|
if (filepathIsFolder == true){
|
|
|
|
// Folder is corrupt
|
2024-08-13 15:25:47 +02:00
|
|
|
return nil, nil, errors.New("Training data is corrupt for attribute: " + attributeName)
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fileName := filesystemObject.Name()
|
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
trainingDataFilenamesMap[fileName] = struct{}{}
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
numberOfTrainingDataFiles := len(trainingDataFilenamesMap)
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
if (numberOfTrainingDataFiles == 0){
|
2024-08-13 15:25:47 +02:00
|
|
|
return nil, nil, errors.New("No training data exists for attribute: " + attributeName)
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
getNumberOfExpectedTrainingDatas := func()(int, error){
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
switch attributeName{
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
case "Eye Color":{
|
|
|
|
return 149894, nil
|
2024-07-05 23:01:43 +02:00
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
}
|
|
|
|
case "Lactose Tolerance":{
|
|
|
|
return 24872, nil
|
|
|
|
}
|
|
|
|
case "Height":{
|
|
|
|
return 92281, nil
|
|
|
|
}
|
|
|
|
case "Autism":{
|
|
|
|
return 32118, nil
|
|
|
|
}
|
|
|
|
case "Homosexualness":{
|
|
|
|
return 14500, nil
|
|
|
|
}
|
2024-08-14 05:37:18 +02:00
|
|
|
case "Obesity":{
|
|
|
|
return 24009, nil
|
|
|
|
}
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
return 0, errors.New("Unknown attributeName: " + attributeName)
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
numberOfExpectedTrainingDatas, err := getNumberOfExpectedTrainingDatas()
|
|
|
|
if (err != nil){ return nil, nil, err }
|
|
|
|
|
|
|
|
if (numberOfTrainingDataFiles != numberOfExpectedTrainingDatas){
|
|
|
|
|
|
|
|
numberOfTrainingDataFilesString := helpers.ConvertIntToString(numberOfTrainingDataFiles)
|
|
|
|
|
2024-08-13 15:25:47 +02:00
|
|
|
return nil, nil, errors.New(attributeName + " quantity of training datas is unexpected: " + numberOfTrainingDataFilesString)
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// We sort the training data to be in a deterministically random order
|
|
|
|
// This allows us to train the neural network in the same order each time
|
|
|
|
// We do this so we can generate deterministic models which are identical byte-for-byte
|
|
|
|
|
|
|
|
// We have to set aside 200 user's training datas for testing the neural network
|
|
|
|
//
|
|
|
|
// We have to remove them per-user because each user has 110 training datas.
|
|
|
|
// Otherwise, we would be training and testing on data from the same users.
|
|
|
|
// We need to test with users that the models were never trained upon.
|
|
|
|
|
|
|
|
// First we extract the user identifiers from the data
|
|
|
|
|
|
|
|
userIdentifiersMap := make(map[int]struct{})
|
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
for trainingDataFilename, _ := range trainingDataFilenamesMap{
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
// Example filepath format: "User4680_TrainingData_89.gob"
|
|
|
|
|
|
|
|
trimmedFilename := strings.TrimPrefix(trainingDataFilename, "User")
|
|
|
|
|
|
|
|
userIdentifierString, _, underscoreExists := strings.Cut(trimmedFilename, "_")
|
|
|
|
if (underscoreExists == false){
|
|
|
|
return nil, nil, errors.New("Invalid trainingData filename: " + trainingDataFilename)
|
|
|
|
}
|
|
|
|
|
|
|
|
userIdentifier, err := helpers.ConvertStringToInt(userIdentifierString)
|
|
|
|
if (err != nil){
|
|
|
|
return nil, nil, errors.New("Invalid trainingData filename: " + trainingDataFilename)
|
|
|
|
}
|
|
|
|
|
|
|
|
userIdentifiersMap[userIdentifier] = struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
userIdentifiersList := helpers.GetListOfMapKeys(userIdentifiersMap)
|
|
|
|
|
|
|
|
// We sort the user identifiers list in ascending order
|
|
|
|
|
|
|
|
slices.Sort(userIdentifiersList)
|
|
|
|
|
|
|
|
// Now we deterministically randomize the order of the user identifiers list
|
2024-05-02 04:45:00 +02:00
|
|
|
pseudorandomNumberGenerator := mathRand.New(mathRand.NewPCG(1, 2))
|
2024-04-11 15:51:56 +02:00
|
|
|
|
|
|
|
pseudorandomNumberGenerator.Shuffle(len(userIdentifiersList), func(i int, j int){
|
|
|
|
userIdentifiersList[i], userIdentifiersList[j] = userIdentifiersList[j], userIdentifiersList[i]
|
|
|
|
})
|
|
|
|
|
|
|
|
trainingSetFilepathsList := make([]string, 0)
|
|
|
|
testingSetFilepathsList := make([]string, 0)
|
|
|
|
|
|
|
|
numberOfUsers := len(userIdentifiersList)
|
|
|
|
|
|
|
|
if (numberOfUsers < 250){
|
2024-08-13 15:25:47 +02:00
|
|
|
return nil, nil, errors.New("Too few training data examples for attribute: " + attributeName)
|
2024-04-11 15:51:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// We use 200 users for testing (validation), so we don't train using them
|
|
|
|
numberOfTrainingUsers := numberOfUsers - 200
|
|
|
|
|
|
|
|
for index, userIdentifier := range userIdentifiersList{
|
|
|
|
|
|
|
|
// Example filepath format: "User4680_TrainingData_89.gob"
|
|
|
|
|
|
|
|
userIdentifierString := helpers.ConvertIntToString(userIdentifier)
|
|
|
|
|
|
|
|
trainingDataFilenamePrefix := "User" + userIdentifierString + "_TrainingData_"
|
|
|
|
|
|
|
|
for k:=1; k <= 110; k++{
|
|
|
|
|
|
|
|
kString := helpers.ConvertIntToString(k)
|
|
|
|
|
|
|
|
trainingDataFilename := trainingDataFilenamePrefix + kString + ".gob"
|
|
|
|
|
2024-07-05 23:01:43 +02:00
|
|
|
_, fileExists := trainingDataFilenamesMap[trainingDataFilename]
|
|
|
|
if (fileExists == false){
|
|
|
|
// Some training datas don't exist due to how training datas are randomly created
|
|
|
|
// Sometimes, no alleles exist, so we skip creating the training data
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-04-11 15:51:56 +02:00
|
|
|
trainingDataFilepath := goFilepath.Join(trainingDataFolderpath, trainingDataFilename)
|
|
|
|
|
|
|
|
if (index < numberOfTrainingUsers){
|
|
|
|
trainingSetFilepathsList = append(trainingSetFilepathsList, trainingDataFilepath)
|
|
|
|
} else {
|
|
|
|
testingSetFilepathsList = append(testingSetFilepathsList, trainingDataFilepath)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return trainingSetFilepathsList, testingSetFilepathsList, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// We use this to define a custom fyne theme
|
|
|
|
// We are only overriding the foreground color to pure black
|
|
|
|
type customTheme struct{
|
|
|
|
defaultTheme fyne.Theme
|
|
|
|
}
|
|
|
|
|
|
|
|
func getCustomFyneTheme()fyne.Theme{
|
|
|
|
|
|
|
|
standardThemeObject := theme.LightTheme()
|
|
|
|
|
|
|
|
newTheme := customTheme{
|
|
|
|
defaultTheme: standardThemeObject,
|
|
|
|
}
|
|
|
|
|
|
|
|
return newTheme
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// This function is used to define our custom fyne themes
|
|
|
|
// It changes a few default colors, while leaving all other colors the same as the default theme
|
|
|
|
func (input customTheme)Color(colorName fyne.ThemeColorName, variant fyne.ThemeVariant)color.Color{
|
|
|
|
|
|
|
|
switch colorName{
|
|
|
|
|
|
|
|
case theme.ColorNameForeground:{
|
|
|
|
|
|
|
|
newColor := color.Black
|
|
|
|
return newColor
|
|
|
|
}
|
|
|
|
case theme.ColorNameSeparator:{
|
|
|
|
|
|
|
|
// This is the color used for separators
|
|
|
|
|
|
|
|
newColor := color.Black
|
|
|
|
return newColor
|
|
|
|
}
|
|
|
|
case theme.ColorNameInputBackground:{
|
|
|
|
|
|
|
|
// This color is used for the background of input elements such as text entries
|
|
|
|
newColor, err := imagery.GetColorObjectFromColorCode("b3b3b3")
|
|
|
|
if (err == nil){
|
|
|
|
return newColor
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case theme.ColorNameButton:{
|
|
|
|
|
|
|
|
// This is the color used for buttons
|
|
|
|
newColor, err := imagery.GetColorObjectFromColorCode("d8d8d8")
|
|
|
|
if (err == nil){
|
|
|
|
return newColor
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case theme.ColorNamePlaceHolder:{
|
|
|
|
|
|
|
|
// This is the color used for text
|
|
|
|
|
|
|
|
newColor, err := imagery.GetColorObjectFromColorCode("4d4d4d")
|
|
|
|
if (err == nil){
|
|
|
|
return newColor
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// We will use the default color for this theme
|
|
|
|
return input.defaultTheme.Color(colorName, variant)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Our custom themes change nothing about the default theme fonts
|
|
|
|
func (input customTheme)Font(style fyne.TextStyle)fyne.Resource{
|
|
|
|
|
|
|
|
themeFont := input.defaultTheme.Font(style)
|
|
|
|
|
|
|
|
return themeFont
|
|
|
|
}
|
|
|
|
|
|
|
|
// Our custom themes change nothing about the default theme icons
|
|
|
|
func (input customTheme)Icon(iconName fyne.ThemeIconName)fyne.Resource{
|
|
|
|
|
|
|
|
themeIcon := input.defaultTheme.Icon(iconName)
|
|
|
|
|
|
|
|
return themeIcon
|
|
|
|
}
|
|
|
|
|
|
|
|
func (input customTheme)Size(name fyne.ThemeSizeName)float32{
|
|
|
|
|
|
|
|
themeSize := input.defaultTheme.Size(name)
|
|
|
|
|
|
|
|
if (name == theme.SizeNameText){
|
|
|
|
|
|
|
|
// After fyne v2.3.0, text labels are no longer the same height as buttons
|
|
|
|
// We increase the text size so that a text label is the same height as a button
|
|
|
|
// We need to increase text size because we are creating grids by creating multiple VBoxes, and connecting them with an HBox
|
|
|
|
//
|
|
|
|
// If we could create grids in a different way, we could avoid having to do this
|
|
|
|
// Example: Create a new grid type: container.NewThinGrid?
|
|
|
|
// -The columns will only be as wide as the the widest element within them
|
|
|
|
// -We can add separators between each row (grid.ShowRowLines = true) or between columns (grid.ShowColumnLines = true)
|
|
|
|
// -We can add borders (grid.ShowTopBorder = true, grid.ShowBottomBorder = true, grid.ShowLeftBorder = true, grid.ShowRightBorder = true)
|
|
|
|
|
|
|
|
// Using a different grid type is the solution we need to eventually use
|
|
|
|
// Then, we can show the user an option to increase the text size globally, and all grids will still render correctly
|
|
|
|
|
|
|
|
result := themeSize * 1.08
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
return themeSize
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|