Added numeric traits to genetic analyses.

This commit is contained in:
Simon Sarasova 2024-08-09 14:23:37 +00:00
parent d769047de7
commit 45e668c05a
No known key found for this signature in database
GPG key ID: EEDA4103C9C36944
23 changed files with 2035 additions and 194 deletions

View file

@ -6,6 +6,7 @@ Small and insignificant changes may not be included in this log.
## Unversioned Changes
* Added numeric traits to genetic analyses. - *Simon Sarasova*
* Improved Documentation.md and Future-Plans.md. - *Simon Sarasova*
* Improved Future-Plans.md. - *Simon Sarasova*
* Added Merkle Tree Payment Proofs to Future-Plans.md. - *Simon Sarasova*

View file

@ -9,4 +9,4 @@ Many other people have written code for modules which are imported by Seekia. Th
Name | Date Of First Commit | Number Of Commits
--- | --- | ---
Simon Sarasova | June 13, 2023 | 274
Simon Sarasova | June 13, 2023 | 275

View file

@ -93,8 +93,7 @@ func setViewCoupleGeneticAnalysisPage(window fyne.Window, person1Identifier stri
setViewCoupleGeneticAnalysisDiscreteTraitsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, currentPage)
})
numericTraitsButton := widget.NewButton("Numeric Traits", func(){
//TODO
showUnderConstructionDialog(window)
setViewCoupleGeneticAnalysisNumericTraitsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, currentPage)
})
categoryButtonsGrid := getContainerCentered(container.NewGridWithColumns(1, generalButton, monogenicDiseasesButton, polygenicDiseasesButton, discreteTraitsButton, numericTraitsButton))
@ -1789,28 +1788,11 @@ func setViewPolygenicDiseaseSampleOffspringRiskScoresChart(window fyne.Window, d
getOffspringSampleRiskScoresChartImage := func()(image.Image, error){
// Map Structure: Risk Score -> Number of offspring with that risk score
offspringRiskScoreCountsMap := make(map[int]int)
for _, offspringRiskScore := range sampleOffspringRiskScoresList{
offspringRiskScoreCountsMap[offspringRiskScore] += 1
}
//TODO: Move StatisticsDatum to its own package, because we are using it for non-user purposes, and will continue to do so
offspringStatisticsDatumsList := make([]statisticsDatum.StatisticsDatum, 0)
for riskScore:=0; riskScore <= 10; riskScore += 1{
getOffspringCount := func()int{
offspringCount, exists := offspringRiskScoreCountsMap[riskScore]
if (exists == false){
return 0
}
return offspringCount
}
offspringCount := getOffspringCount()
offspringCount := helpers.CountMatchingElementsInSlice(sampleOffspringRiskScoresList, riskScore)
riskScoreString := helpers.ConvertIntToString(riskScore)
offspringCountString := helpers.ConvertIntToString(offspringCount)
@ -1917,9 +1899,8 @@ func setViewCoupleGeneticAnalysisDiscreteTraitsPage(window fyne.Window, person1N
traitRulesList := traitObject.RulesList
if (len(traitLociList) == 0 && len(traitRulesList) == 0){
// This trait does not have any rules
// This trait does not have any loci or rules
// We cannot analyze it yet
// We will add neural network prediction so we can predict these traits
continue
}
@ -2133,6 +2114,18 @@ func setViewCoupleGeneticAnalysisDiscreteTraitDetailsPage(window fyne.Window, pe
})
traitNameRow := container.NewHBox(layout.NewSpacer(), traitNameLabel, traitNameText, traitNameInfoButton, layout.NewSpacer())
traitObject, err := traits.GetTraitObject(traitName)
if (err != nil){
setErrorEncounteredPage(window, err, previousPage)
return
}
traitIsDiscreteOrNumeric := traitObject.DiscreteOrNumeric
if (traitIsDiscreteOrNumeric != "Discrete"){
setErrorEncounteredPage(window, errors.New("setViewCoupleGeneticAnalysisDiscreteTraitDetailsPage called with non-discrete trait: " + traitName), previousPage)
return
}
neuralNetworkExists, _ := geneticPredictionModels.GetGeneticPredictionModelBytes(traitName)
emptyLabel1 := widget.NewLabel("")
@ -2183,14 +2176,6 @@ func setViewCoupleGeneticAnalysisDiscreteTraitDetailsPage(window fyne.Window, pe
pairNameColumn.Add(genomePairNameLabel)
viewDetailsButtonsColumn.Add(viewAnalysisDetailsButton)
traitObject, err := traits.GetTraitObject(traitName)
if (err != nil) { return err }
traitIsDiscreteOrNumeric := traitObject.DiscreteOrNumeric
if (traitIsDiscreteOrNumeric != "Discrete"){
return errors.New("setViewCoupleGeneticAnalysisDiscreteTraitDetailsPage called with non-discrete trait: " + traitName)
}
neuralNetworkExists, neuralNetworkAnalysisExists, offspringOutcomeProbabilitiesMap_NeuralNetwork, neuralNetworkPredictionConfidence, _, _, anyRulesExist, rulesAnalysisExists, offspringOutcomeProbabilitiesMap_Rules, _, quantityOfRulesTested, _, _, err := readGeneticAnalysis.GetOffspringDiscreteTraitInfoFromGeneticAnalysis(coupleAnalysisObject, traitName, genomePairIdentifier)
if (err != nil) { return err }
if (neuralNetworkExists == false && anyRulesExist == false){
@ -2951,5 +2936,637 @@ func setViewCoupleGeneticAnalysisDiscreteTraitRuleDetailsPage(window fyne.Window
}
func setViewCoupleGeneticAnalysisNumericTraitsPage(window fyne.Window, person1Name string, person2Name string, person1AnalysisObject geneticAnalysis.PersonAnalysis, person2AnalysisObject geneticAnalysis.PersonAnalysis, coupleAnalysisObject geneticAnalysis.CoupleAnalysis, previousPage func()){
currentPage := func(){setViewCoupleGeneticAnalysisNumericTraitsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, previousPage)}
title := getPageTitleCentered("Viewing Genetic Analysis - Numeric Traits")
backButton := getBackButtonCentered(previousPage)
description := getLabelCentered("Below is an analysis of the average numeric trait outcomes for the couple's offspring.")
getTraitsGrid := func()(*fyne.Container, error){
pair1Person1GenomeIdentifier, pair1Person2GenomeIdentifier, secondGenomePairExists, _, _, _, _, _, _, _, _, err := readGeneticAnalysis.GetMetadataFromCoupleGeneticAnalysis(coupleAnalysisObject)
if (err != nil){ return nil, err }
mainGenomePairIdentifier := helpers.JoinTwo16ByteArrays(pair1Person1GenomeIdentifier, pair1Person2GenomeIdentifier)
emptyLabel1 := widget.NewLabel("")
traitNameLabel := getItalicLabelCentered("Trait Name")
emptyLabel2 := widget.NewLabel("")
predictedOutcomeLabel := getItalicLabelCentered("Predicted Outcome")
quantityOfLabel := getItalicLabelCentered("Quantity Of")
lociKnownLabel := getItalicLabelCentered("Loci Known")
emptyLabel3 := widget.NewLabel("")
confidenceRangeLabel := getItalicLabelCentered("Confidence Range")
emptyLabel4 := widget.NewLabel("")
conflictExistsLabel := getItalicLabelCentered("Conflict Exists?")
emptyLabel5 := widget.NewLabel("")
emptyLabel6 := widget.NewLabel("")
traitNameColumn := container.NewVBox(emptyLabel1, traitNameLabel, widget.NewSeparator())
predictedOutcomeColumn := container.NewVBox(emptyLabel2, predictedOutcomeLabel, widget.NewSeparator())
confidenceRangeColumn := container.NewVBox(emptyLabel3, confidenceRangeLabel, widget.NewSeparator())
quantityOfLociKnownColumn := container.NewVBox(quantityOfLabel, lociKnownLabel, widget.NewSeparator())
conflictExistsColumn := container.NewVBox(emptyLabel4, conflictExistsLabel, widget.NewSeparator())
viewDetailsButtonsColumn := container.NewVBox(emptyLabel5, emptyLabel6, widget.NewSeparator())
traitObjectsList, err := traits.GetTraitObjectsList()
if (err != nil) { return nil, err }
for _, traitObject := range traitObjectsList{
traitIsDiscreteOrNumeric := traitObject.DiscreteOrNumeric
if (traitIsDiscreteOrNumeric != "Numeric"){
continue
}
traitLociList := traitObject.LociList
if (len(traitLociList) == 0){
// This trait does not have any loci
// We cannot analyze it yet
continue
}
traitName := traitObject.TraitName
neuralNetworkExists, _ := geneticPredictionModels.GetGeneticPredictionModelBytes(traitName)
if (neuralNetworkExists == false){
// We cannot analyze this trait
continue
}
traitNameLabel := getBoldLabelCentered(traitName)
analysisExists, offspringAverageOutcome, predictionConfidenceRangesMap, quantityOfLociKnown, _, _, conflictExists, err := readGeneticAnalysis.GetOffspringNumericTraitInfoFromGeneticAnalysis(coupleAnalysisObject, traitName, mainGenomePairIdentifier)
if (err != nil) { return nil, err }
getPredictedOutcomeLabel := func()fyne.Widget{
if (analysisExists == false){
result := widget.NewLabel("Unknown")
return result
}
predictedOutcomeString := helpers.ConvertFloat64ToStringRounded(offspringAverageOutcome, 2)
predictedOutcomeFormatted := predictedOutcomeString + " centimeters"
outcomeLabel := getBoldLabel(predictedOutcomeFormatted)
return outcomeLabel
}
predictedOutcomeLabel := getPredictedOutcomeLabel()
predictedOutcomeLabelCentered := getWidgetCentered(predictedOutcomeLabel)
getConfidenceRangeLabel := func()(fyne.Widget, error){
if (analysisExists == false){
result := widget.NewLabel("Unknown")
return result, nil
}
// This is a list of the percentage accuracies in the map
// For example: 80% == The distance from the prediction you must travel for 80% of the predictions to be
// accurate within that range
confidenceRangePercentagesList := helpers.GetListOfMapKeys(predictionConfidenceRangesMap)
// We sort the list so the percentage is always the same upon refreshing the page
slices.Sort(confidenceRangePercentagesList)
closestToEightyPercentage, err := helpers.GetClosestIntInList(confidenceRangePercentagesList, 80)
if (err != nil) { return nil, err }
closestToEightyPercentageConfidenceDistance, exists := predictionConfidenceRangesMap[closestToEightyPercentage]
if (exists == false){
return nil, errors.New("GetListOfMapKeys returning list of elements which contains element which is not in the map.")
}
closestConfidenceDistanceString := helpers.ConvertFloat64ToStringRounded(closestToEightyPercentageConfidenceDistance, 2)
closestToEightyPercentageString := helpers.ConvertIntToString(closestToEightyPercentage)
confidenceRangeLabelValueFormatted := "+/- " + closestConfidenceDistanceString + " (" + closestToEightyPercentageString + "% Confidence)"
confidenceRangeLabel := getBoldLabel(confidenceRangeLabelValueFormatted)
return confidenceRangeLabel, nil
}
confidenceRangeLabel, err := getConfidenceRangeLabel()
if (err != nil) { return nil, err }
confidenceRangeLabelCentered := getWidgetCentered(confidenceRangeLabel)
totalQuantityOfLoci := len(traitLociList)
quantityOfLociKnownString := helpers.ConvertIntToString(quantityOfLociKnown)
totalQuantityOfLociString := helpers.ConvertIntToString(totalQuantityOfLoci)
quantityOfLociKnownFormatted := quantityOfLociKnownString + "/" + totalQuantityOfLociString
quantityOfLociKnownLabel := getBoldLabelCentered(quantityOfLociKnownFormatted)
conflictExistsString := helpers.ConvertBoolToYesOrNoString(conflictExists)
conflictExistsLabel := getBoldLabelCentered(conflictExistsString)
viewDetailsButton := getWidgetCentered(widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
setViewCoupleGeneticAnalysisNumericTraitDetailsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, traitName, currentPage)
}))
traitNameColumn.Add(traitNameLabel)
predictedOutcomeColumn.Add(predictedOutcomeLabelCentered)
confidenceRangeColumn.Add(confidenceRangeLabelCentered)
quantityOfLociKnownColumn.Add(quantityOfLociKnownLabel)
conflictExistsColumn.Add(conflictExistsLabel)
viewDetailsButtonsColumn.Add(viewDetailsButton)
traitNameColumn.Add(widget.NewSeparator())
predictedOutcomeColumn.Add(widget.NewSeparator())
confidenceRangeColumn.Add(widget.NewSeparator())
quantityOfLociKnownColumn.Add(widget.NewSeparator())
conflictExistsColumn.Add(widget.NewSeparator())
viewDetailsButtonsColumn.Add(widget.NewSeparator())
}
predictedOutcomeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
//TODO
showUnderConstructionDialog(window)
})
predictedOutcomeColumn.Add(predictedOutcomeHelpButton)
confidenceRangeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
//TODO
showUnderConstructionDialog(window)
})
confidenceRangeColumn.Add(confidenceRangeHelpButton)
quantityOfLociKnownHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
//TODO
showUnderConstructionDialog(window)
})
quantityOfLociKnownColumn.Add(quantityOfLociKnownHelpButton)
traitsGrid := container.NewHBox(layout.NewSpacer(), traitNameColumn, predictedOutcomeColumn, confidenceRangeColumn, quantityOfLociKnownColumn)
if (secondGenomePairExists == true){
conflictExistsHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
setCoupleGeneticAnalysisConflictExistsExplainerPage(window, currentPage)
})
conflictExistsColumn.Add(conflictExistsHelpButton)
traitsGrid.Add(conflictExistsColumn)
}
traitsGrid.Add(viewDetailsButtonsColumn)
traitsGrid.Add(layout.NewSpacer())
return traitsGrid, nil
}
traitsGrid, err := getTraitsGrid()
if (err != nil){
setErrorEncounteredPage(window, err, previousPage)
return
}
page := container.NewVBox(title, backButton, widget.NewSeparator(), description, widget.NewSeparator(), traitsGrid)
setPageContent(page, window)
}
func setViewCoupleGeneticAnalysisNumericTraitDetailsPage(window fyne.Window, person1Name string, person2Name string, person1AnalysisObject geneticAnalysis.PersonAnalysis, person2AnalysisObject geneticAnalysis.PersonAnalysis, coupleAnalysisObject geneticAnalysis.CoupleAnalysis, traitName string, previousPage func()){
currentPage := func(){setViewCoupleGeneticAnalysisNumericTraitDetailsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, traitName, previousPage)}
title := getPageTitleCentered("Viewing Couple Analysis - " + traitName)
backButton := getBackButtonCentered(previousPage)
pair1Person1GenomeIdentifier, pair1Person2GenomeIdentifier, secondGenomePairExists, pair2Person1GenomeIdentifier, pair2Person2GenomeIdentifier, _, _, _, _, _, _, err := readGeneticAnalysis.GetMetadataFromCoupleGeneticAnalysis(coupleAnalysisObject)
if (err != nil){
setErrorEncounteredPage(window, err, previousPage)
return
}
getDescriptionSection := func()*fyne.Container{
if (secondGenomePairExists == false){
description := getLabelCentered("Below is the trait analysis for the couple's offspring.")
return description
}
description1 := getLabelCentered("Below is the trait analysis for the couple's offspring.")
description2 := getLabelCentered("Each genome pair combines different genomes from each person.")
descriptionsSection := container.NewVBox(description1, description2)
return descriptionsSection
}
descriptionSection := getDescriptionSection()
traitNameLabel := widget.NewLabel("Trait:")
traitNameText := getBoldLabel(traitName)
traitNameInfoButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){
setViewTraitDetailsPage(window, traitName, currentPage)
})
traitNameRow := container.NewHBox(layout.NewSpacer(), traitNameLabel, traitNameText, traitNameInfoButton, layout.NewSpacer())
neuralNetworkExists, _ := geneticPredictionModels.GetGeneticPredictionModelBytes(traitName)
if (neuralNetworkExists == false){
// We cannot analyze this trait
setErrorEncounteredPage(window, errors.New("setViewCoupleGeneticAnalysisNumericTraitDetailsPage called non-analyzable trait: " + traitName), previousPage)
return
}
traitObject, err := traits.GetTraitObject(traitName)
if (err != nil){
setErrorEncounteredPage(window, err, previousPage)
return
}
traitIsDiscreteOrNumeric := traitObject.DiscreteOrNumeric
if (traitIsDiscreteOrNumeric != "Numeric"){
setErrorEncounteredPage(window, errors.New("setViewCoupleGeneticAnalysisNumericTraitDetailsPage called with non-numeric trait: " + traitName), previousPage)
return
}
traitLociList := traitObject.LociList
emptyLabel1 := widget.NewLabel("")
emptyLabel2 := widget.NewLabel("")
emptyLabel3 := widget.NewLabel("")
genomePairLabel := getItalicLabelCentered("Genome Pair")
emptyLabel4 := widget.NewLabel("")
predictedOutcomeLabel := getItalicLabelCentered("Predicted Outcome")
emptyLabel5 := widget.NewLabel("")
confidenceRangeLabel := getItalicLabelCentered("Confidence Range")
quantityOfLabel := getItalicLabelCentered("Quantity Of")
lociKnownLabel := getItalicLabelCentered("Loci Known")
emptyLabel6 := widget.NewLabel("")
emptyLabel7 := widget.NewLabel("")
emptyLabel8 := widget.NewLabel("")
emptyLabel9 := widget.NewLabel("")
viewGenomePairButtonsColumn := container.NewVBox(emptyLabel1, emptyLabel2, widget.NewSeparator())
pairNameColumn := container.NewVBox(emptyLabel3, genomePairLabel, widget.NewSeparator())
predictedOutcomeColumn := container.NewVBox(emptyLabel4, predictedOutcomeLabel, widget.NewSeparator())
predictionConfidenceRangeColumn := container.NewVBox(emptyLabel5, confidenceRangeLabel, widget.NewSeparator())
quantityOfLociKnownColumn := container.NewVBox(quantityOfLabel, lociKnownLabel, widget.NewSeparator())
viewSampleOffspringButtonsColumn := container.NewVBox(emptyLabel6, emptyLabel7, widget.NewSeparator())
viewDetailsButtonsColumn := container.NewVBox(emptyLabel8, emptyLabel9, widget.NewSeparator())
addGenomePairRow := func(genomePairName string, person1GenomeIdentifier [16]byte, person2GenomeIdentifier [16]byte)error{
genomePairIdentifier := helpers.JoinTwo16ByteArrays(person1GenomeIdentifier, person2GenomeIdentifier)
genomePairNameLabel := getBoldLabelCentered(genomePairName)
viewGenomePairButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){
//TODO
showUnderConstructionDialog(window)
})
analysisExists, offspringAverageOutcome, predictionConfidenceRangesMap, quantityOfLociKnown, _, sampleOffspringOutcomesList, _, err := readGeneticAnalysis.GetOffspringNumericTraitInfoFromGeneticAnalysis(coupleAnalysisObject, traitName, genomePairIdentifier)
if (err != nil) { return err }
getPredictedOutcomeLabel := func()fyne.Widget{
if (analysisExists == false){
unknownLabel := widget.NewLabel("Unknown")
return unknownLabel
}
offspringAverageOutcomeString := helpers.ConvertFloat64ToStringRounded(offspringAverageOutcome, 2)
offspringAverageOutcomeFormatted := offspringAverageOutcomeString + " centimeters"
predictedOutcomeLabel := getBoldLabel(offspringAverageOutcomeFormatted)
return predictedOutcomeLabel
}
predictedOutcomeLabel := getPredictedOutcomeLabel()
predictedOutcomeLabelCentered := getWidgetCentered(predictedOutcomeLabel)
getConfidenceRangeLabel := func()(fyne.Widget, error){
if (analysisExists == false){
result := widget.NewLabel("Unknown")
return result, nil
}
// This is a list of the percentage accuracies in the map
// For example: 80% == The distance from the prediction you must travel for 80% of the predictions to be
// accurate within that range
confidenceRangePercentagesList := helpers.GetListOfMapKeys(predictionConfidenceRangesMap)
// We sort the list so the percentage is always the same upon refreshing the page
slices.Sort(confidenceRangePercentagesList)
closestToEightyPercentage, err := helpers.GetClosestIntInList(confidenceRangePercentagesList, 80)
if (err != nil) { return nil, err }
closestToEightyPercentageConfidenceDistance, exists := predictionConfidenceRangesMap[closestToEightyPercentage]
if (exists == false){
return nil, errors.New("GetListOfMapKeys returning list of elements which contains element which is not in the map.")
}
closestConfidenceDistanceString := helpers.ConvertFloat64ToStringRounded(closestToEightyPercentageConfidenceDistance, 2)
closestToEightyPercentageString := helpers.ConvertIntToString(closestToEightyPercentage)
confidenceRangeLabelValueFormatted := "+/- " + closestConfidenceDistanceString + " (" + closestToEightyPercentageString + "% Confidence)"
confidenceRangeLabel := getBoldLabel(confidenceRangeLabelValueFormatted)
return confidenceRangeLabel, nil
}
confidenceRangeLabel, err := getConfidenceRangeLabel()
if (err != nil) { return err }
confidenceRangeLabelCentered := getWidgetCentered(confidenceRangeLabel)
totalQuantityOfLoci := len(traitLociList)
quantityOfLociKnownString := helpers.ConvertIntToString(quantityOfLociKnown)
totalQuantityOfLociString := helpers.ConvertIntToString(totalQuantityOfLoci)
quantityOfLociKnownFormatted := quantityOfLociKnownString + "/" + totalQuantityOfLociString
quantityOfLociKnownLabel := getBoldLabelCentered(quantityOfLociKnownFormatted)
viewSampleOffspringsButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){
setViewNumericTraitSampleOffspringRiskScoresChart(window, traitName, sampleOffspringOutcomesList, quantityOfLociKnown, currentPage)
})
viewAnalysisDetailsButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
//TODO
showUnderConstructionDialog(window)
})
viewGenomePairButtonsColumn.Add(viewGenomePairButton)
pairNameColumn.Add(genomePairNameLabel)
predictedOutcomeColumn.Add(predictedOutcomeLabelCentered)
predictionConfidenceRangeColumn.Add(confidenceRangeLabelCentered)
quantityOfLociKnownColumn.Add(quantityOfLociKnownLabel)
viewSampleOffspringButtonsColumn.Add(viewSampleOffspringsButton)
viewDetailsButtonsColumn.Add(viewAnalysisDetailsButton)
viewGenomePairButtonsColumn.Add(widget.NewSeparator())
pairNameColumn.Add(widget.NewSeparator())
predictedOutcomeColumn.Add(widget.NewSeparator())
predictionConfidenceRangeColumn.Add(widget.NewSeparator())
quantityOfLociKnownColumn.Add(widget.NewSeparator())
viewSampleOffspringButtonsColumn.Add(widget.NewSeparator())
viewDetailsButtonsColumn.Add(widget.NewSeparator())
return nil
}
err = addGenomePairRow("Pair 1", pair1Person1GenomeIdentifier, pair1Person2GenomeIdentifier)
if (err != nil) {
setErrorEncounteredPage(window, err, previousPage)
return
}
if (secondGenomePairExists == true){
err := addGenomePairRow("Pair 2", pair2Person1GenomeIdentifier, pair2Person2GenomeIdentifier)
if (err != nil) {
setErrorEncounteredPage(window, err, previousPage)
return
}
}
predictedOutcomeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
//TODO
showUnderConstructionDialog(window)
})
predictedOutcomeColumn.Add(predictedOutcomeHelpButton)
confidenceRangeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
//TODO
showUnderConstructionDialog(window)
})
predictionConfidenceRangeColumn.Add(confidenceRangeHelpButton)
quantityOfLociKnownHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
//TODO
showUnderConstructionDialog(window)
})
quantityOfLociKnownColumn.Add(quantityOfLociKnownHelpButton)
genomesContainer := container.NewHBox(layout.NewSpacer(), viewGenomePairButtonsColumn, pairNameColumn, predictedOutcomeColumn, predictionConfidenceRangeColumn, quantityOfLociKnownColumn, viewSampleOffspringButtonsColumn, viewDetailsButtonsColumn, layout.NewSpacer())
page := container.NewVBox(title, backButton, widget.NewSeparator(), descriptionSection, widget.NewSeparator(), traitNameRow, widget.NewSeparator(), genomesContainer)
setPageContent(page, window)
}
// This is a page that shows the user 100 sample offspring trait outcomes on a bar chart
// This helps users to visualize the standard deviation of their offspring's trait outcomes with this user
func setViewNumericTraitSampleOffspringRiskScoresChart(window fyne.Window, traitName string, sampleOffspringOutcomesList []float64, quantityOfLociKnown int, previousPage func()){
currentPage := func(){setViewNumericTraitSampleOffspringRiskScoresChart(window, traitName, sampleOffspringOutcomesList, quantityOfLociKnown, previousPage)}
title := getPageTitleCentered("Viewing Sample Offspring Outcomes Chart")
backButton := getBackButtonCentered(previousPage)
description := widget.NewLabel("Below is a chart of 100 sample offspring trait outcomes for this disease.")
descriptionHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
//TODO
showUnderConstructionDialog(window)
})
descriptionRow := container.NewHBox(layout.NewSpacer(), description, descriptionHelpButton, layout.NewSpacer())
traitNameTitle := widget.NewLabel("Trait Name:")
traitNameLabel := getBoldLabel(traitName)
traitNameRow := container.NewHBox(layout.NewSpacer(), traitNameTitle, traitNameLabel, layout.NewSpacer())
if (len(sampleOffspringOutcomesList) == 0){
description2 := getBoldLabelCentered("There is no offspring information available for this trait.")
description3 := getBoldLabelCentered("This is because there were no trait loci for which both prospective parents had information.")
page := container.NewVBox(title, backButton, widget.NewSeparator(), descriptionRow, widget.NewSeparator(), traitNameRow, widget.NewSeparator(), description2, description3)
setPageContent(page, window)
return
}
traitObject, err := traits.GetTraitObject(traitName)
if (err != nil){
setErrorEncounteredPage(window, err, previousPage)
return
}
traitLociList := traitObject.LociList
totalNumberOfLoci := len(traitLociList)
totalNumberOfLociString := helpers.ConvertIntToString(totalNumberOfLoci)
numberOfLociKnownTitle := widget.NewLabel("Quantity Of Loci Known:")
numberOfLociKnownString := helpers.ConvertIntToString(quantityOfLociKnown)
numberOfLociKnownLabel := getBoldLabel(numberOfLociKnownString + "/" + totalNumberOfLociString)
lociKnownHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
//TODO
showUnderConstructionDialog(window)
})
quantityOfLociKnownRow := container.NewHBox(layout.NewSpacer(), numberOfLociKnownTitle, numberOfLociKnownLabel, lociKnownHelpButton, layout.NewSpacer())
getOffspringSampleOutcomesChartImage := func()(image.Image, error){
if (len(sampleOffspringOutcomesList) != 100){
return nil, errors.New("setViewNumericTraitSampleOffspringRiskScoresChart called with offspringOutcomesList that is not 100 items in length.")
}
// We sort the list in ascending order
slices.Sort(sampleOffspringOutcomesList)
getOffspringStatisticsDatumsList := func()([]statisticsDatum.StatisticsDatum){
offspringStatisticsDatumsList := make([]statisticsDatum.StatisticsDatum, 0)
smallestItem := sampleOffspringOutcomesList[0]
largestItem := sampleOffspringOutcomesList[99]
if (smallestItem == largestItem){
// We can't split the values into groups
// Every offspring has the same value
onlyValueString := helpers.ConvertFloat64ToStringRounded(smallestItem, 2)
onlyValueFormatted := onlyValueString + " centimeters"
newStatisticsDatum := statisticsDatum.StatisticsDatum{
Label: onlyValueString,
LabelFormatted: onlyValueFormatted,
Value: float64(100),
ValueFormatted: "100",
}
offspringStatisticsDatumsList = append(offspringStatisticsDatumsList, newStatisticsDatum)
return offspringStatisticsDatumsList
}
// We split all outcomes into ten groups
sizeOfEachGroup := (largestItem-smallestItem)/10
index := float64(0)
for {
getGroupUpperBound := func()float64{
if (len(offspringStatisticsDatumsList) == 9){
// We are adding the last datum
return largestItem
}
groupUpperBound := index + sizeOfEachGroup
return groupUpperBound
}
groupUpperBound := getGroupUpperBound()
offspringInGroupCount := 0
for _, outcomeValue := range sampleOffspringOutcomesList{
if (outcomeValue >= index && outcomeValue < groupUpperBound){
offspringInGroupCount += 1
}
}
groupLowerBoundString := helpers.ConvertFloat64ToStringRounded(index, 1)
groupUpperBoundString := helpers.ConvertFloat64ToStringRounded(groupUpperBound, 1)
groupDescription := groupLowerBoundString + "-" + groupUpperBoundString + " centimeters"
offspringCountString := helpers.ConvertIntToString(offspringInGroupCount)
newStatisticsDatum := statisticsDatum.StatisticsDatum{
Label: groupDescription,
LabelFormatted: groupDescription,
Value: float64(offspringInGroupCount),
ValueFormatted: offspringCountString,
}
offspringStatisticsDatumsList = append(offspringStatisticsDatumsList, newStatisticsDatum)
if (len(offspringStatisticsDatumsList) == 10){
break
}
index += sizeOfEachGroup
}
return offspringStatisticsDatumsList
}
offspringStatisticsDatumsList := getOffspringStatisticsDatumsList()
chartTitle := traitName + ": 100 Prospective Offspring Values"
formatYAxisValuesFunction := func(inputTraitValue float64)(string, error){
result := helpers.ConvertFloat64ToStringRounded(inputTraitValue, 2)
return result, nil
}
offspringsChart, err := createCharts.CreateBarChart(chartTitle, offspringStatisticsDatumsList, formatYAxisValuesFunction, true, " Offspring")
if (err != nil) { return nil, err }
return offspringsChart, nil
}
offspringOutcomesChartImage, err := getOffspringSampleOutcomesChartImage()
if (err != nil){
setErrorEncounteredPage(window, err, previousPage)
return
}
viewChartFullscreenButton := getWidgetCentered(widget.NewButtonWithIcon("View Fullscreen", theme.ZoomInIcon(), func(){
setViewFullpageImagePage(window, offspringOutcomesChartImage, currentPage)
}))
pageHeader := container.NewVBox(title, backButton, widget.NewSeparator(), descriptionRow, widget.NewSeparator(), traitNameRow, widget.NewSeparator(), quantityOfLociKnownRow, widget.NewSeparator())
chartFyneImage := canvas.NewImageFromImage(offspringOutcomesChartImage)
chartFyneImage.FillMode = canvas.ImageFillContain
page := container.NewBorder(pageHeader, viewChartFullscreenButton, nil, nil, chartFyneImage)
setPageContent(page, window)
}

View file

@ -26,8 +26,10 @@ import "seekia/internal/genetics/myPeople"
import "seekia/internal/genetics/readGeneticAnalysis"
import "seekia/internal/helpers"
import "slices"
import "errors"
func setViewPersonGeneticAnalysisPage(window fyne.Window, personIdentifier string, analysisObject geneticAnalysis.PersonAnalysis, numberOfGenomesAnalyzed int, previousPage func()){
appMemory.SetMemoryEntry("CurrentViewedPage", "ViewGeneticAnalysisPage")
@ -75,8 +77,7 @@ func setViewPersonGeneticAnalysisPage(window fyne.Window, personIdentifier strin
setViewPersonGeneticAnalysisDiscreteTraitsPage(window, personIdentifier, analysisObject, currentPage)
})
numericTraitsButton := widget.NewButton("Numeric Traits", func(){
//TODO
showUnderConstructionDialog(window)
setViewPersonGeneticAnalysisNumericTraitsPage(window, personIdentifier, analysisObject, currentPage)
})
categoryButtonsGrid := getContainerCentered(container.NewGridWithColumns(1, generalButton, monogenicDiseasesButton, polygenicDiseasesButton, discreteTraitsButton, numericTraitsButton))
@ -2672,3 +2673,230 @@ func setViewPersonGeneticAnalysisDiscreteTraitRuleDetailsPage(window fyne.Window
}
func setViewPersonGeneticAnalysisNumericTraitsPage(window fyne.Window, personIdentifier string, analysisObject geneticAnalysis.PersonAnalysis, previousPage func()){
currentPage := func(){setViewPersonGeneticAnalysisNumericTraitsPage(window, personIdentifier, analysisObject, previousPage)}
title := getPageTitleCentered("Viewing Genetic Analysis - Numeric Traits")
backButton := getBackButtonCentered(previousPage)
description := getLabelCentered("Below is an analysis of the numeric traits for this person's genome.")
getTraitsContainer := func()(*fyne.Container, error){
allRawGenomeIdentifiersList, multipleGenomesExist, onlyExcludeConflictsGenomeIdentifier, _, _, err := readGeneticAnalysis.GetMetadataFromPersonGeneticAnalysis(analysisObject)
if (err != nil){ return nil, err }
// Outputs:
// -[16]byte: Main genome identifier (Is either the combined Only exclude conflicts genome or the only genome)
// -error
getMainGenomeIdentifier := func()([16]byte, error){
if (multipleGenomesExist == true){
return onlyExcludeConflictsGenomeIdentifier, nil
}
// Only 1 genome exists
genomeIdentifier := allRawGenomeIdentifiersList[0]
return genomeIdentifier, nil
}
mainGenomeIdentifier, err := getMainGenomeIdentifier()
if (err != nil){ return nil, err }
emptyLabel1 := widget.NewLabel("")
traitNameLabel := getItalicLabelCentered("Trait Name")
emptyLabel2 := widget.NewLabel("")
predictedOutcomeLabel := getItalicLabelCentered("Predicted Outcome")
quantityOfLabel := getItalicLabelCentered("Quantity Of")
lociKnownLabel := getItalicLabelCentered("Loci Known")
emptyLabel3 := widget.NewLabel("")
confidenceRangeLabel := getItalicLabelCentered("Confidence Range")
emptyLabel4 := widget.NewLabel("")
conflictExistsLabel := getItalicLabelCentered("Conflict Exists?")
emptyLabel5 := widget.NewLabel("")
emptyLabel6 := widget.NewLabel("")
traitNameColumn := container.NewVBox(emptyLabel1, traitNameLabel, widget.NewSeparator())
predictedOutcomeColumn := container.NewVBox(emptyLabel2, predictedOutcomeLabel, widget.NewSeparator())
confidenceRangeColumn := container.NewVBox(emptyLabel3, confidenceRangeLabel, widget.NewSeparator())
quantityOfLociKnownColumn := container.NewVBox(quantityOfLabel, lociKnownLabel, widget.NewSeparator())
conflictExistsColumn := container.NewVBox(emptyLabel4, conflictExistsLabel, widget.NewSeparator())
viewButtonsColumn := container.NewVBox(emptyLabel5, emptyLabel6, widget.NewSeparator())
traitObjectsList, err := traits.GetTraitObjectsList()
if (err != nil) { return nil, err }
for _, traitObject := range traitObjectsList{
traitIsDiscreteOrNumeric := traitObject.DiscreteOrNumeric
if (traitIsDiscreteOrNumeric != "Numeric"){
continue
}
traitName := traitObject.TraitName
traitLociList := traitObject.LociList
if (len(traitLociList) == 0){
// This trait does not have any loci to analyze
// We cannot analyze it yet
continue
}
traitNameText := getBoldLabelCentered(traitName)
neuralNetworkExists, _ := geneticPredictionModels.GetGeneticPredictionModelBytes(traitName)
if (neuralNetworkExists == false){
// This trait has no neural network
// We cannot analyze it
continue
}
analysisExists, predictedOutcome, confidenceRangesMap, quantityOfLociKnown, _, conflictExists, err := readGeneticAnalysis.GetPersonNumericTraitInfoFromGeneticAnalysis(analysisObject, traitName, mainGenomeIdentifier)
if (err != nil) { return nil, err }
getPredictionLabel := func()fyne.Widget{
if (analysisExists == false){
result := getItalicLabel("Unknown")
return result
}
predictedOutcomeString := helpers.ConvertFloat64ToStringRounded(predictedOutcome, 2)
//TODO: Fix units
result := getBoldLabel(predictedOutcomeString + " centimeters")
return result
}
predictionLabel := getPredictionLabel()
predictionLabelCentered := getWidgetCentered(predictionLabel)
getConfidenceRangeLabel := func()(fyne.Widget, error){
if (analysisExists == false){
unknownLabel := widget.NewLabel("Unknown")
return unknownLabel, nil
}
// This is a list of the percentage accuracies in the map
// For example: 80% == The distance from the prediction you must travel for 80% of the predictions to be
// accurate within that range
confidenceRangePercentagesList := helpers.GetListOfMapKeys(confidenceRangesMap)
// We sort the list so the percentage is always the same upon refreshing the page
slices.Sort(confidenceRangePercentagesList)
closestToEightyPercentage, err := helpers.GetClosestIntInList(confidenceRangePercentagesList, 80)
if (err != nil) { return nil, err }
closestToEightyPercentageConfidenceDistance, exists := confidenceRangesMap[closestToEightyPercentage]
if (exists == false){
return nil, errors.New("GetListOfMapKeys returning list of elements which contains element which is not in the map.")
}
closestConfidenceDistanceString := helpers.ConvertFloat64ToStringRounded(closestToEightyPercentageConfidenceDistance, 2)
closestToEightyPercentageString := helpers.ConvertIntToString(closestToEightyPercentage)
confidenceRangeLabelValueFormatted := "+/- " + closestConfidenceDistanceString + " (" + closestToEightyPercentageString + "% Confidence)"
confidenceRangeLabel := getBoldLabel(confidenceRangeLabelValueFormatted)
return confidenceRangeLabel, nil
}
confidenceRangeLabel, err := getConfidenceRangeLabel()
if (err != nil) { return nil, err }
confidenceRangeLabelCentered := getWidgetCentered(confidenceRangeLabel)
totalQuantityOfLoci := len(traitLociList)
quantityOfLociKnownString := helpers.ConvertIntToString(quantityOfLociKnown)
totalQuantityOfLociString := helpers.ConvertIntToString(totalQuantityOfLoci)
quantityOfLociKnownFormatted := quantityOfLociKnownString + "/" + totalQuantityOfLociString
quantityOfLociKnownLabel := getBoldLabelCentered(quantityOfLociKnownFormatted)
conflictExistsString := helpers.ConvertBoolToYesOrNoString(conflictExists)
conflictExistsLabel := getBoldLabelCentered(conflictExistsString)
viewDetailsButton := getWidgetCentered(widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
showUnderConstructionDialog(window)
//TODO
}))
traitNameColumn.Add(traitNameText)
predictedOutcomeColumn.Add(predictionLabelCentered)
confidenceRangeColumn.Add(confidenceRangeLabelCentered)
quantityOfLociKnownColumn.Add(quantityOfLociKnownLabel)
conflictExistsColumn.Add(conflictExistsLabel)
viewButtonsColumn.Add(viewDetailsButton)
traitNameColumn.Add(widget.NewSeparator())
predictedOutcomeColumn.Add(widget.NewSeparator())
confidenceRangeColumn.Add(widget.NewSeparator())
quantityOfLociKnownColumn.Add(widget.NewSeparator())
conflictExistsColumn.Add(widget.NewSeparator())
viewButtonsColumn.Add(widget.NewSeparator())
}
predictedOutcomeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
//TODO
showUnderConstructionDialog(window)
})
predictedOutcomeColumn.Add(predictedOutcomeHelpButton)
confidenceRangeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
//TODO
showUnderConstructionDialog(window)
})
confidenceRangeColumn.Add(confidenceRangeHelpButton)
quantityOfLociKnownHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
//TODO
showUnderConstructionDialog(window)
})
quantityOfLociKnownColumn.Add(quantityOfLociKnownHelpButton)
traitsContainer := container.NewHBox(layout.NewSpacer(), traitNameColumn, predictedOutcomeColumn, confidenceRangeColumn, quantityOfLociKnownColumn)
if (multipleGenomesExist == true){
conflictExistsHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
setPersonGeneticAnalysisConflictExistsExplainerPage(window, currentPage)
})
conflictExistsColumn.Add(conflictExistsHelpButton)
traitsContainer.Add(conflictExistsColumn)
}
traitsContainer.Add(viewButtonsColumn)
traitsContainer.Add(layout.NewSpacer())
return traitsContainer, nil
}
traitsContainer, err := getTraitsContainer()
if (err != nil){
setErrorEncounteredPage(window, err, previousPage)
return
}
page := container.NewVBox(title, backButton, widget.NewSeparator(), description, widget.NewSeparator(), traitsContainer)
setPageContent(page, window)
}

View file

@ -3555,8 +3555,7 @@ func setViewMateProfilePage_GeneticTraits(window fyne.Window, getAnyUserProfileA
})
numericTraitsButton := widget.NewButton("Numeric Traits", func(){
//TODO
showUnderConstructionDialog(window)
setViewMateProfilePage_NumericGeneticTraits(window, "Offspring", getAnyUserProfileAttributeFunction, currentPage)
})
buttonsGrid := getContainerCentered(container.NewGridWithColumns(1, discreteTraitsButton, numericTraitsButton))
@ -4400,6 +4399,364 @@ func setViewMateProfilePage_DiscreteTraitRules(window fyne.Window, traitName str
setPageContent(page, window)
}
func setViewMateProfilePage_NumericGeneticTraits(window fyne.Window, userOrOffspring string, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){
if (userOrOffspring != "User" && userOrOffspring != "Offspring"){
setErrorEncounteredPage(window, errors.New("setViewMateProfilePage_NumericGeneticTraits called with invalid userOrOffspring: " + userOrOffspring), previousPage)
return
}
if (userOrOffspring == "Offspring"){
setLoadingScreen(window, "View Profile - Physical", "Computing Genetic Analysis...")
}
//currentPage := func(){setViewMateProfilePage_NumericGeneticTraits(window, userOrOffspring, getAnyUserProfileAttributeFunction, previousPage)}
title := getPageTitleCentered(translate("View Profile - Physical"))
backButton := getBackButtonCentered(previousPage)
subtitle := getPageSubtitleCentered(translate("Numeric Genetic Traits"))
description1 := getLabelCentered("Below is the numeric genetic trait analysis for this user.")
description2 := getLabelCentered("You can choose to view the analysis of the user or an offspring between you and the user.")
description3 := getLabelCentered("You must link your genome person in the Build Profile menu to see offspring information.")
handleSelectButton := func(newUserOrOffspring string){
if (userOrOffspring == newUserOrOffspring){
return
}
setViewMateProfilePage_NumericGeneticTraits(window, newUserOrOffspring, getAnyUserProfileAttributeFunction, previousPage)
}
userOrOffspringSelector := widget.NewSelect([]string{"User", "Offspring"}, handleSelectButton)
userOrOffspringSelector.Selected = userOrOffspring
userOrOffspringSelectorCentered := getWidgetCentered(userOrOffspringSelector)
getTraitsInfoGrid := func()(*fyne.Container, error){
myPersonChosen, myGenomesExist, myAnalysisIsReady, myAnalysisObject, myGenomeIdentifier, _, err := myChosenAnalysis.GetMyChosenMateGeneticAnalysis()
if (err != nil) { return nil, err }
//Outputs:
// -map[int64]locusValue.LocusValue
// -error
getMyGenomeLocusValuesMap := func()(map[int64]locusValue.LocusValue, error){
if (myPersonChosen == false || myGenomesExist == false || myAnalysisIsReady == false){
emptyMap := make(map[int64]locusValue.LocusValue)
return emptyMap, nil
}
_, _, _, _, myGenomesMap, err := readGeneticAnalysis.GetMetadataFromPersonGeneticAnalysis(myAnalysisObject)
if (err != nil) { return nil, err }
myGenomeLocusValuesMap, exists := myGenomesMap[myGenomeIdentifier]
if (exists == false){
return nil, errors.New("GetMyChosenMateGeneticAnalysis returning analysis which is missing genome with myGenomeIdentifier.")
}
return myGenomeLocusValuesMap, nil
}
myGenomeLocusValuesMap, err := getMyGenomeLocusValuesMap()
if (err != nil) { return nil, err }
emptyLabel1 := widget.NewLabel("")
traitNameLabel := getItalicLabelCentered("Trait Name")
emptyLabel2 := widget.NewLabel("")
userPredictedOutcomeTitle := getItalicLabelCentered("User Predicted Outcome")
emptyLabel3 := widget.NewLabel("")
offspringPredictedOutcomeTitle := getItalicLabelCentered("Offspring Predicted Outcome")
predictionTitle := getItalicLabelCentered("Prediction")
confidenceRangeTitle := getItalicLabelCentered("Confidence Range")
quantityOfLabel1 := getItalicLabelCentered("Quantity Of")
lociKnownLabel := getItalicLabelCentered("Loci Known")
emptyLabel4 := widget.NewLabel("")
emptyLabel5 := widget.NewLabel("")
traitNameColumn := container.NewVBox(emptyLabel1, traitNameLabel, widget.NewSeparator())
userPredictedOutcomeColumn := container.NewVBox(emptyLabel2, userPredictedOutcomeTitle, widget.NewSeparator())
offspringPredictedOutcomeColumn := container.NewVBox(emptyLabel3, offspringPredictedOutcomeTitle, widget.NewSeparator())
predictionConfidenceRangeColumn := container.NewVBox(predictionTitle, confidenceRangeTitle, widget.NewSeparator())
quantityOfLociKnownColumn := container.NewVBox(quantityOfLabel1, lociKnownLabel, widget.NewSeparator())
viewTraitDetailsButtonsColumn := container.NewVBox(emptyLabel4, emptyLabel5, widget.NewSeparator())
traitObjectsList, err := traits.GetTraitObjectsList()
if (err != nil) { return nil, err }
for _, traitObject := range traitObjectsList{
traitName := traitObject.TraitName
traitIsDiscreteOrNumeric := traitObject.DiscreteOrNumeric
if (traitIsDiscreteOrNumeric != "Numeric"){
continue
}
traitLociList := traitObject.LociList
numberOfTraitLoci := len(traitLociList)
if (numberOfTraitLoci == 0){
// We are not able to analyze these traits
continue
}
traitNeuralNetworkExists, _ := geneticPredictionModels.GetGeneticPredictionModelBytes(traitName)
if (traitNeuralNetworkExists == false){
// We are not able to analyze these traits yet
continue
}
traitNameText := getBoldLabelCentered(translate(traitName))
// We construct the user's trait locus values map
// Map Structure: Locus rsID -> locusValue.LocusValue
userTraitLocusValuesMap := make(map[int64]locusValue.LocusValue)
for _, rsID := range traitLociList{
rsIDString := helpers.ConvertInt64ToString(rsID)
userLocusValueAttributeName := "LocusValue_rs" + rsIDString
userLocusValueIsKnown, _, userLocusValue, err := getAnyUserProfileAttributeFunction(userLocusValueAttributeName)
if (err != nil) { return nil, err }
if (userLocusValueIsKnown == false){
continue
}
userLocusBase1, userLocusBase2, semicolonFound := strings.Cut(userLocusValue, ";")
if (semicolonFound == false){
return nil, errors.New("Database corrupt: Contains profile with invalid " + userLocusValueAttributeName + " value: " + userLocusValue)
}
userLocusIsPhasedAttributeName := "LocusIsPhased_rs" + rsIDString
userLocusIsPhasedExists, _, userLocusIsPhasedString, err := getAnyUserProfileAttributeFunction(userLocusIsPhasedAttributeName)
if (err != nil) { return nil, err }
if (userLocusIsPhasedExists == false){
return nil, errors.New("Database corrupt: Contains profile with locusValue but not locusIsPhased status for locus: " + rsIDString)
}
userLocusIsPhased, err := helpers.ConvertYesOrNoStringToBool(userLocusIsPhasedString)
if (err != nil) { return nil, err }
userLocusValueObject := locusValue.LocusValue{
Base1Value: userLocusBase1,
Base2Value: userLocusBase2,
LocusIsPhased: userLocusIsPhased,
}
userTraitLocusValuesMap[rsID] = userLocusValueObject
}
//Outputs:
// -bool: Analysis results exist
// -float64: Predicted outcome
// -map[int]float64: Prediction confidence ranges map
// -int: Quantity of known loci
// -error
getAnalysisResults := func()(bool, float64, map[int]float64, int, error){
if (userOrOffspring == "User"){
traitNeuralNetworkExists, anyLocusValuesAreKnown, predictedOutcome, predictionAccuracyRangesMap, quantityOfLociKnown, _, err := createPersonGeneticAnalysis.GetGenomeNumericTraitAnalysis(traitObject, userTraitLocusValuesMap, true)
if (err != nil) { return false, 0, nil, 0, err }
if (traitNeuralNetworkExists == false){
return false, 0, nil, 0, errors.New("GetGenomeNumericTraitAnalysis claims neural network doesn't exist for trait, but we already checked.")
}
return anyLocusValuesAreKnown, predictedOutcome, predictionAccuracyRangesMap, quantityOfLociKnown, nil
} else {
// userOrOffspring == "Offspring"
neuralNetworkExists, anyLociKnown, predictedOutcome, predictionAccuracyRangesMap, _, quantityOfLociKnown, _, err := createCoupleGeneticAnalysis.GetOffspringNumericTraitInfo(traitObject, userTraitLocusValuesMap, myGenomeLocusValuesMap)
if (err != nil) { return false, 0, nil, 0, err }
if (neuralNetworkExists == false){
return false, 0, nil, 0, errors.New("GetOffspringTraitInfo_NeuralNetwork claiming that neural network doesn't exist when we already checked.")
}
return anyLociKnown, predictedOutcome, predictionAccuracyRangesMap, quantityOfLociKnown, nil
}
}
analysisExists, predictedOutcome, predictionConfidenceRangesMap, quantityOfLociKnown, err := getAnalysisResults()
if (err != nil) { return nil, err }
getPredictedOutcomeLabel := func()fyne.Widget{
if (analysisExists == false){
unknownLabel := widget.NewLabel("Unknown")
return unknownLabel
}
predictedOutcomeString := helpers.ConvertFloat64ToStringRounded(predictedOutcome, 2)
//TODO: Retrieve units from traits package
predictedOutcomeFormatted := predictedOutcomeString + " centimeters"
predictedOutcomeLabel := getBoldLabel(predictedOutcomeFormatted)
return predictedOutcomeLabel
}
predictedOutcomeLabel := getPredictedOutcomeLabel()
predictedOutcomeLabelCentered := getWidgetCentered(predictedOutcomeLabel)
getConfidenceRangeLabel := func()(fyne.Widget, error){
if (analysisExists == false){
result := widget.NewLabel("Unknown")
return result, nil
}
// This is a list of the percentage accuracies in the map
// For example: 80% == The distance from the prediction you must travel for 80% of the predictions to be
// accurate within that range
confidenceRangePercentagesList := helpers.GetListOfMapKeys(predictionConfidenceRangesMap)
// We sort the list so the percentage is always the same upon refreshing the page
slices.Sort(confidenceRangePercentagesList)
closestToEightyPercentage, err := helpers.GetClosestIntInList(confidenceRangePercentagesList, 80)
if (err != nil) { return nil, err }
closestToEightyPercentageConfidenceDistance, exists := predictionConfidenceRangesMap[closestToEightyPercentage]
if (exists == false){
return nil, errors.New("GetListOfMapKeys returning list of elements which contains element which is not in the map.")
}
closestConfidenceDistanceString := helpers.ConvertFloat64ToStringRounded(closestToEightyPercentageConfidenceDistance, 2)
closestToEightyPercentageString := helpers.ConvertIntToString(closestToEightyPercentage)
confidenceRangeLabelValueFormatted := "+/- " + closestConfidenceDistanceString + " (" + closestToEightyPercentageString + "% Confidence)"
confidenceRangeLabel := getBoldLabel(confidenceRangeLabelValueFormatted)
return confidenceRangeLabel, nil
}
confidenceRangeLabel, err := getConfidenceRangeLabel()
if (err != nil) { return nil, err }
confidenceRangeLabelCentered := getWidgetCentered(confidenceRangeLabel)
totalNumberOfLociString := helpers.ConvertIntToString(numberOfTraitLoci)
quantityOfLociKnownString := helpers.ConvertIntToString(quantityOfLociKnown)
quantityOfLociKnownFormatted := quantityOfLociKnownString + "/" + totalNumberOfLociString
quantityOfLociKnownLabel := getBoldLabelCentered(quantityOfLociKnownFormatted)
viewTraitDetailsButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
//TODO
showUnderConstructionDialog(window)
})
traitNameColumn.Add(traitNameText)
if (userOrOffspring == "User"){
userPredictedOutcomeColumn.Add(predictedOutcomeLabelCentered)
} else {
offspringPredictedOutcomeColumn.Add(predictedOutcomeLabelCentered)
}
predictionConfidenceRangeColumn.Add(confidenceRangeLabelCentered)
quantityOfLociKnownColumn.Add(quantityOfLociKnownLabel)
viewTraitDetailsButtonsColumn.Add(viewTraitDetailsButton)
traitNameColumn.Add(widget.NewSeparator())
userPredictedOutcomeColumn.Add(widget.NewSeparator())
offspringPredictedOutcomeColumn.Add(widget.NewSeparator())
predictionConfidenceRangeColumn.Add(widget.NewSeparator())
quantityOfLociKnownColumn.Add(widget.NewSeparator())
viewTraitDetailsButtonsColumn.Add(widget.NewSeparator())
}
if (userOrOffspring == "User"){
predictedOutcomeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
//TODO
showUnderConstructionDialog(window)
})
userPredictedOutcomeColumn.Add(predictedOutcomeHelpButton)
} else {
// userOrOffspring == "Offspring"
predictedOutcomeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
//TODO
showUnderConstructionDialog(window)
})
offspringPredictedOutcomeColumn.Add(predictedOutcomeHelpButton)
}
predictionConfidenceRangeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
//TODO
showUnderConstructionDialog(window)
})
predictionConfidenceRangeColumn.Add(predictionConfidenceRangeHelpButton)
quantityOfLociKnownHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
//TODO
showUnderConstructionDialog(window)
})
quantityOfLociKnownColumn.Add(quantityOfLociKnownHelpButton)
traitsInfoGrid := container.NewHBox(layout.NewSpacer(), traitNameColumn)
if (userOrOffspring == "User"){
traitsInfoGrid.Add(userPredictedOutcomeColumn)
} else {
// userOrOffspring == "Offspring"
traitsInfoGrid.Add(offspringPredictedOutcomeColumn)
}
traitsInfoGrid.Add(predictionConfidenceRangeColumn)
traitsInfoGrid.Add(quantityOfLociKnownColumn)
traitsInfoGrid.Add(viewTraitDetailsButtonsColumn)
traitsInfoGrid.Add(layout.NewSpacer())
return traitsInfoGrid, nil
}
traitsInfoGrid, err := getTraitsInfoGrid()
if (err != nil){
setErrorEncounteredPage(window, err, previousPage)
return
}
page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), userOrOffspringSelectorCentered, widget.NewSeparator(), traitsInfoGrid)
setPageContent(page, window)
}
func setViewMateProfilePage_Diet(window fyne.Window, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){
title := getPageTitleCentered(translate("View Profile - Lifestyle"))

View file

@ -41,7 +41,7 @@ func CreateCoupleGeneticAnalysis(person1GenomesList []prepareRawGenomes.RawGenom
person1PrepareRawGenomesUpdatePercentageCompleteFunction := func(newPercentage int)error{
newPercentageCompletion, err := helpers.ScaleIntProportionally(true, newPercentage, 0, 100, 0, 25)
if (err != nil){ return err }
if (err != nil) { return err }
err = updatePercentageCompleteFunction(newPercentageCompletion)
if (err != nil) { return err }
@ -742,6 +742,104 @@ func CreateCoupleGeneticAnalysis(person1GenomesList []prepareRawGenomes.RawGenom
}
offspringDiscreteTraitsMap[traitName] = newOffspringTraitInfoObject
} else {
// traitIsDiscreteOrNumeric == "Numeric"
// This map stores the trait info for each genome pair
// Map Structure: Genome Pair Identifier -> OffspringGenomePairNumericTraitInfo
offspringTraitInfoMap := make(map[[32]byte]geneticAnalysis.OffspringGenomePairNumericTraitInfo)
// This will add the offspring trait information for the provided genome pair to the offspringTraitInfoMap
addGenomePairTraitInfoToOffspringMap := func(person1GenomeIdentifier [16]byte, person2GenomeIdentifier [16]byte)error{
person1LocusValuesMap, exists := person1GenomesMap[person1GenomeIdentifier]
if (exists == false){
return errors.New("addGenomePairTraitInfoToOffspringMap called with unknown person1GenomeIdentifier.")
}
person2LocusValuesMap, exists := person2GenomesMap[person2GenomeIdentifier]
if (exists == false){
return errors.New("addGenomePairTraitInfoToOffspringMap called with unknown person2GenomeIdentifier.")
}
neuralNetworkExists, neuralNetworkAnalysisExists, averageOutcome, predictionConfidenceRangesMap, sampleOffspringOutcomesList, quantityOfLociTested, quantityOfParentalPhasedLoci, err := GetOffspringNumericTraitInfo(traitObject, person1LocusValuesMap, person2LocusValuesMap)
if (err != nil) { return err }
if (neuralNetworkExists == false){
// Predictions are not possible for this trait
return nil
}
if (neuralNetworkAnalysisExists == false){
// No locations for this trait exists in which both user's genomes contain information
return nil
}
newOffspringGenomePairTraitInfo := geneticAnalysis.OffspringGenomePairNumericTraitInfo{
OffspringAverageOutcome: averageOutcome,
PredictionConfidenceRangesMap: predictionConfidenceRangesMap,
QuantityOfParentalPhasedLoci: quantityOfParentalPhasedLoci,
QuantityOfLociKnown: quantityOfLociTested,
SampleOffspringOutcomesList: sampleOffspringOutcomesList,
}
genomePairIdentifier := helpers.JoinTwo16ByteArrays(person1GenomeIdentifier, person2GenomeIdentifier)
offspringTraitInfoMap[genomePairIdentifier] = newOffspringGenomePairTraitInfo
return nil
}
err = addGenomePairTraitInfoToOffspringMap(pair1Person1GenomeIdentifier, pair1Person2GenomeIdentifier)
if (err != nil) { return false, "", err }
if (genomePair2Exists == true){
err := addGenomePairTraitInfoToOffspringMap(pair2Person1GenomeIdentifier, pair2Person2GenomeIdentifier)
if (err != nil) { return false, "", err }
}
newOffspringTraitInfoObject := geneticAnalysis.OffspringNumericTraitInfo{
TraitInfoMap: offspringTraitInfoMap,
}
if (len(offspringTraitInfoMap) >= 2){
// We check for conflicts
// Conflicts are only possible if two genome pairs exist with information about the trait
checkIfConflictExists := func()(bool, error){
// We check for conflicts between each genome pair's outcome scores and trait rules maps
genomePairTraitInfoObject := geneticAnalysis.OffspringGenomePairNumericTraitInfo{}
firstItemReached := false
for _, currentGenomePairTraitInfoObject := range offspringTraitInfoMap{
if (firstItemReached == false){
genomePairTraitInfoObject = currentGenomePairTraitInfoObject
firstItemReached = true
continue
}
areEqual := reflect.DeepEqual(genomePairTraitInfoObject, currentGenomePairTraitInfoObject)
if (areEqual == false){
return true, nil
}
}
return false, nil
}
conflictExists, err := checkIfConflictExists()
if (err != nil) { return false, "", err }
newOffspringTraitInfoObject.ConflictExists = conflictExists
}
offspringNumericTraitsMap[traitName] = newOffspringTraitInfoObject
}
}
@ -1323,6 +1421,110 @@ func GetOffspringDiscreteTraitInfo_Rules(traitObject traits.Trait, person1LocusV
}
//Outputs:
// -bool: A neural network exists for this trait
// -bool: Analysis exists (at least 1 locus exists for this analysis from both people's genomes
// -float64: Average outcome for offspring
// -map[int]float64: Prediction accuracy ranges map
// -Map Structure: Probability prediction is accurate (X) -> Distance from predictoin that must be travelled in both directions to
// create a range in which the true value will fall into, X% of the time
// -[]float64: A list of 100 offspring outcomes
// -int: Quantity of loci known
// -int: Quantity of parental phased loci
// -error
func GetOffspringNumericTraitInfo(traitObject traits.Trait, person1LocusValuesMap map[int64]locusValue.LocusValue, person2LocusValuesMap map[int64]locusValue.LocusValue)(bool, bool, float64, map[int]float64, []float64, int, int, error){
traitName := traitObject.TraitName
traitIsDiscreteOrNumeric := traitObject.DiscreteOrNumeric
if (traitIsDiscreteOrNumeric != "Numeric"){
return false, false, 0, nil, nil, 0, 0, errors.New("GetOffspringNumericTraitInfo called with non-numeric trait.")
}
modelExists, _ := geneticPredictionModels.GetGeneticPredictionModelBytes(traitName)
if (modelExists == false){
// Prediction is not possible for this trait
return false, false, 0, nil, nil, 0, 0, nil
}
traitLociList := traitObject.LociList
// First we count up the quantity of parental phased loci
// We only count the quantity of phased loci for loci which are known for both parents
quantityOfParentalPhasedLoci := 0
for _, rsID := range traitLociList{
person1LocusValue, exists := person1LocusValuesMap[rsID]
if (exists == false){
continue
}
person2LocusValue, exists := person2LocusValuesMap[rsID]
if (exists == false){
continue
}
person1LocusIsPhased := person1LocusValue.LocusIsPhased
if (person1LocusIsPhased == true){
quantityOfParentalPhasedLoci += 1
}
person2LocusIsPhased := person2LocusValue.LocusIsPhased
if (person2LocusIsPhased == true){
quantityOfParentalPhasedLoci += 1
}
}
// Next, we create 100 prospective offspring genomes.
anyLocusValueExists, prospectiveOffspringGenomesList, err := getProspectiveOffspringGenomesList(traitLociList, person1LocusValuesMap, person2LocusValuesMap)
if (err != nil) { return false, false, 0, nil, nil, 0, 0, err }
if (anyLocusValueExists == false){
return true, false, 0, nil, nil, 0, 0, nil
}
// A list of predicted outcomes for each offspring
predictedOutcomesList := make([]float64, 0)
accuracyRangesMap := make(map[int]float64)
quantityOfLociTested := 0
for index, offspringGenomeMap := range prospectiveOffspringGenomesList{
neuralNetworkExists, predictionIsKnown, predictedOutcome, predictionAccuracyRangesMap, currentQuantityOfLociTested, _, err := createPersonGeneticAnalysis.GetGenomeNumericTraitAnalysis(traitObject, offspringGenomeMap, false)
if (err != nil){ return false, false, 0, nil, nil, 0, 0, err }
if (neuralNetworkExists == false){
return false, false, 0, nil, nil, 0, 0, errors.New("GetGenomeNumericTraitAnalysis claiming that neural network doesn't exist when we already checked.")
}
if (predictionIsKnown == false){
return false, false, 0, nil, nil, 0, 0, errors.New("GetGenomeNumericTraitAnalysis claiming that prediction is impossible when we already know at least 1 locus value exists for trait.")
}
predictedOutcomesList = append(predictedOutcomesList, predictedOutcome)
if (index == 0){
// These values should be the same for each predicted offspring
accuracyRangesMap = predictionAccuracyRangesMap
quantityOfLociTested = currentQuantityOfLociTested
}
}
// We calculate the average predicted outcome
outcomesSum := float64(0)
for _, predictedOutcome := range predictedOutcomesList{
outcomesSum += predictedOutcome
}
averageOutcome := outcomesSum/100
return true, true, averageOutcome, accuracyRangesMap, predictedOutcomesList, quantityOfLociTested, quantityOfParentalPhasedLoci, nil
}
// This function will return a list of 100 prospective offspring genomes
// Each genome represents an equal-probability offspring genome from both people's genomes
// This function takes into account the effects of genetic linkage

View file

@ -150,6 +150,14 @@ func CreatePersonGeneticAnalysis(genomesList []prepareRawGenomes.RawGenomeWithMe
if (err != nil) { return false, "", err }
analysisDiscreteTraitsMap[traitName] = personTraitAnalysisObject
} else {
//traitIsDiscreteOrNumeric == "Numeric"
personTraitAnalysisObject, err := GetPersonNumericTraitAnalysis(genomesWithMetadataList, traitObject)
if (err != nil) { return false, "", err }
analysisNumericTraitsMap[traitName] = personTraitAnalysisObject
}
}
@ -993,6 +1001,79 @@ func GetPersonDiscreteTraitAnalysis(inputGenomesWithMetadataList []prepareRawGen
}
//Outputs:
// -geneticAnalysis.PersonNumericTraitInfo: Trait analysis object
// -error
func GetPersonNumericTraitAnalysis(inputGenomesWithMetadataList []prepareRawGenomes.GenomeWithMetadata, traitObject traits.Trait)(geneticAnalysis.PersonNumericTraitInfo, error){
// Map Structure: Genome Identifier -> PersonGenomeNumericTraitInfo
newPersonTraitInfoMap := make(map[[16]byte]geneticAnalysis.PersonGenomeNumericTraitInfo)
for _, genomeWithMetadataObject := range inputGenomesWithMetadataList{
genomeIdentifier := genomeWithMetadataObject.GenomeIdentifier
genomeMap := genomeWithMetadataObject.GenomeMap
neuralNetworkExists, neuralNetworkOutcomeIsKnown, predictedOutcome, predictionConfidenceRangesMap, quantityOfLociKnown, quantityOfPhasedLoci, err := GetGenomeNumericTraitAnalysis(traitObject, genomeMap, true)
if (err != nil) { return geneticAnalysis.PersonNumericTraitInfo{}, err }
if (neuralNetworkExists == false || neuralNetworkOutcomeIsKnown == false){
continue
}
newPersonGenomeTraitInfo := geneticAnalysis.PersonGenomeNumericTraitInfo{
PredictedOutcome: predictedOutcome,
ConfidenceRangesMap: predictionConfidenceRangesMap,
QuantityOfLociKnown: quantityOfLociKnown,
QuantityOfPhasedLoci: quantityOfPhasedLoci,
}
newPersonTraitInfoMap[genomeIdentifier] = newPersonGenomeTraitInfo
}
newPersonTraitInfoObject := geneticAnalysis.PersonNumericTraitInfo{
TraitInfoMap: newPersonTraitInfoMap,
}
if (len(newPersonTraitInfoMap) <= 1){
// We do not need to check for conflicts, there is only <=1 genome with trait information
// Nothing left to do. Analysis is complete.
return newPersonTraitInfoObject, nil
}
// We check for conflicts
getConflictExistsBool := func()(bool, error){
// We check to see if the analysis results are the same for all genomes
firstItemReached := false
personGenomeTraitInfoObject := geneticAnalysis.PersonGenomeNumericTraitInfo{}
for _, genomeTraitInfoObject := range newPersonTraitInfoMap{
if (firstItemReached == false){
personGenomeTraitInfoObject = genomeTraitInfoObject
continue
}
areEqual := reflect.DeepEqual(personGenomeTraitInfoObject, genomeTraitInfoObject)
if (areEqual == false){
return true, nil
}
}
return false, nil
}
conflictExists, err := getConflictExistsBool()
if (err != nil) { return geneticAnalysis.PersonNumericTraitInfo{}, err }
newPersonTraitInfoObject.ConflictExists = conflictExists
return newPersonTraitInfoObject, nil
}
//Outputs:
// -int: Base pair disease locus risk weight
// -bool: Base pair disease locus odds ratio known
@ -1021,7 +1102,7 @@ func GetGenomePolygenicDiseaseLocusRiskInfo(locusRiskWeightsMap map[string]int,
return riskWeight, true, oddsRatio, nil
}
// We use this to generate trait predictions using a neural network
// We use this to generate discrete trait predictions using a neural network
// The alternative prediction method is to use Rules (see GetGenomeTraitAnalysis_Rules)
//Outputs:
// -bool: Trait Neural network analysis available (if false, we can't predict this trait using a neural network)
@ -1263,6 +1344,65 @@ func GetGenomePassesDiscreteTraitRuleStatus(ruleLociList []traits.RuleLocus, gen
}
// We use this to generate numeric trait predictions using a neural network
//Outputs:
// -bool: Trait Neural network analysis available (if false, we can't predict this trait using a neural network)
// -bool: Neural network outcome is known (at least 1 locus value is known which is needed for the neural network
// -float64: The predicted value (Example: Height in centimeters)
// -map[int]float64: Accuracy ranges map
// -Map Structure: Probability prediction is accurate (X) -> Distance from predictoin that must be travelled in both directions to
// create a range in which the true value will fall into, X% of the time
// -int: Quantity of loci known
// -int: Quantity of phased loci
// -error
func GetGenomeNumericTraitAnalysis(traitObject traits.Trait, genomeMap map[int64]locusValue.LocusValue, checkForAliases bool)(bool, bool, float64, map[int]float64, int, int, error){
getGenomeLocusValuesMap := func()(map[int64]locusValue.LocusValue, error){
if (checkForAliases == false){
// We don't need to check for rsID aliases.
return genomeMap, nil
}
traitLociList := traitObject.LociList
// This map contains the locus values for the genome
// If a locus's entry doesn't exist, its value is unknown
// Map Structure: Locus rsID -> Locus Value
genomeLocusValuesMap := make(map[int64]locusValue.LocusValue)
for _, locusRSID := range traitLociList{
locusBasePairKnown, _, _, _, locusValueObject, err := GetLocusValueFromGenomeMap(checkForAliases, genomeMap, locusRSID)
if (err != nil) { return nil, err }
if (locusBasePairKnown == false){
continue
}
genomeLocusValuesMap[locusRSID] = locusValueObject
}
return genomeLocusValuesMap, nil
}
genomeLocusValuesMap, err := getGenomeLocusValuesMap()
if (err != nil) { return false, false, 0, nil, 0, 0, err }
traitName := traitObject.TraitName
neuralNetworkModelExists, traitPredictionIsPossible, predictedOutcome, predictionAccuracyRangesMap, quantityOfLociKnown, quantityOfPhasedLoci, err := geneticPrediction.GetNeuralNetworkNumericTraitPredictionFromGenomeMap(traitName, genomeLocusValuesMap)
if (err != nil) { return false, false, 0, nil, 0, 0, err }
if (neuralNetworkModelExists == false){
return false, false, 0, nil, 0, 0, nil
}
if (traitPredictionIsPossible == false){
return true, false, 0, nil, 0, 0, nil
}
return true, true, predictedOutcome, predictionAccuracyRangesMap, quantityOfLociKnown, quantityOfPhasedLoci, nil
}
// This function will retrieve the base pair of the locus from the input genome map
// We use this function because each rsID has aliases, so we must sometimes check those aliases to find locus values
//

View file

@ -469,15 +469,15 @@ type OffspringGenomePairNumericTraitInfo struct{
// predicted value's range to be accurate, X% of the time?
// For example: 50% accuracy requires a +/-5 point range, 80% accuracy requires a +-15 point range
// Map Structure: Accuracy probability (0-100) -> Amount to add to value in both +/- directions so prediction is that accurate
AverageConfidenceRangesMap map[int]float64
PredictionConfidenceRangesMap map[int]float64
QuantityOfLociKnown int
// This describes the quantity of loci from both parents that are phased
// For example, if there are 10 loci for this trait, and one parent has 10 phased loci and the other has 5,
// this variable will have a value of 15
QuantityOfParentalPhasedLoci int
QuantityOfLociKnown int
// A list of 100 offspring outcomes for 100 prospective offspring from the genome pair
// Example: A list of heights for 100 prospective offspring
SampleOffspringOutcomesList []float64

View file

@ -4,7 +4,7 @@
package geneticPrediction
// I am a neophyte in the ways of neural networks.
// I am a novice in the ways of neural networks.
// Machine learning experts should chime in and offer improvements.
// We have to make sure that model inference remains very fast
// Sorting matches by offspring total polygenic disease score will require inference on dozens of models for each match
@ -27,7 +27,6 @@ import "encoding/gob"
import "slices"
import "errors"
//import "log"
type NeuralNetwork struct{
@ -283,13 +282,9 @@ func DecodeBytesToDiscreteTraitPredictionAccuracyInfoMap(inputBytes []byte)(Disc
return newDiscreteTraitPredictionAccuracyInfoMap, nil
}
type NumericTraitPredictionAccuracyInfoMap map[NumericTraitOutcomeInfo]NumericTraitPredictionAccuracyRangesMap
type NumericTraitPredictionAccuracyInfoMap map[NumericTraitPredictionInfo]NumericTraitPredictionAccuracyRangesMap
type NumericTraitOutcomeInfo struct{
// This is the outcome which was predicted
// Example: 150 centimeters
OutcomeValue float64
type NumericTraitPredictionInfo struct{
// This is a value between 0-100 which describes the percentage of the loci which were tested for the input for the prediction
PercentageOfLociTested int
@ -377,54 +372,8 @@ func GetNeuralNetworkDiscreteTraitPredictionFromGenomeMap(traitName string, geno
traitRSIDsListCopy := slices.Clone(traitRSIDsList)
slices.Sort(traitRSIDsListCopy)
// In the inputLayer, each locus value is represented by 3 neurons:
// 1. LocusExists/LocusIsPhased
// -0 = Locus value is unknown
// -0.5 = Locus Is known, phase is unknown
// -1 = Locus Is Known, phase is known
// 2. Allele1 Locus Value (Value between 0-1)
// -0 = Value is unknown
// 3. Allele2 Locus Value (Value between 0-1)
// -0 = Value is unknown
//
neuralNetworkInput := make([]float32, 0)
quantityOfLociKnown := 0
quantityOfPhasedLoci := 0
for _, rsID := range traitRSIDsListCopy{
userLocusValue, exists := genomeMap[rsID]
if (exists == false){
neuralNetworkInput = append(neuralNetworkInput, 0, 0, 0)
continue
}
quantityOfLociKnown += 1
locusAllele1 := userLocusValue.Base1Value
locusAllele2 := userLocusValue.Base2Value
locusIsPhased := userLocusValue.LocusIsPhased
getNeuron1 := func()float32{
if (locusIsPhased == false){
return 0.5
}
quantityOfPhasedLoci += 1
return 1
}
neuron1 := getNeuron1()
neuron2, err := convertAlleleToNeuron(locusAllele1)
if (err != nil) { return false, false, "", 0, 0, 0, err }
neuron3, err := convertAlleleToNeuron(locusAllele2)
if (err != nil) { return false, false, "", 0, 0, 0, err }
neuralNetworkInput = append(neuralNetworkInput, neuron1, neuron2, neuron3)
}
neuralNetworkInput, quantityOfLociKnown, quantityOfPhasedLoci, err := createInputNeuralNetworkLayerFromGenomeMap(traitRSIDsListCopy, genomeMap)
if (err != nil) { return false, false, "", 0, 0, 0, err }
if (quantityOfLociKnown == 0){
// We can't predict anything about this trait for this genome
@ -522,6 +471,179 @@ func GetNeuralNetworkDiscreteTraitPredictionFromGenomeMap(traitName string, geno
return true, true, predictedOutcomeName, predictionAccuracy, quantityOfLociKnown, quantityOfPhasedLoci, nil
}
//Outputs:
// -bool: Neural network model exists for this trait (trait prediction is possible for this trait)
// -bool: Trait prediction is possible for this user (User has at least 1 known trait locus value)
// -float64: Predicted trait outcome (Example: Height in centimeters)
// -map[int]float64: Accuracy ranges map
// -Map Structure: Probability prediction is accurate (X) -> Distance from prediction that must be travelled in both directions to
// create a range in which the true value will fall into, X% of the time
// -int: Quantity of loci known
// -int: Quantity of phased loci
// -error
func GetNeuralNetworkNumericTraitPredictionFromGenomeMap(traitName string, genomeMap map[int64]locusValue.LocusValue)(bool, bool, float64, map[int]float64, int, int, error){
traitObject, err := traits.GetTraitObject(traitName)
if (err != nil) { return false, false, 0, nil, 0, 0, err }
traitIsDiscreteOrNumeric := traitObject.DiscreteOrNumeric
if (traitIsDiscreteOrNumeric != "Numeric"){
return false, false, 0, nil, 0, 0, errors.New("GetNeuralNetworkNumericTraitPredictionFromGenomeMap called with non-discrete trait: " + traitName)
}
// This is a map of rsIDs which influence this trait
traitRSIDsList := traitObject.LociList
if (len(traitRSIDsList) == 0){
// Prediction is not possible for this trait
return false, false, 0, nil, 0, 0, nil
}
predictionModelExists, predictionModelBytes := geneticPredictionModels.GetGeneticPredictionModelBytes(traitName)
if (predictionModelExists == false){
// Prediction is not possible for this trait
return false, false, 0, nil, 0, 0, nil
}
traitRSIDsListCopy := slices.Clone(traitRSIDsList)
slices.Sort(traitRSIDsListCopy)
neuralNetworkInput, quantityOfLociKnown, quantityOfPhasedLoci, err := createInputNeuralNetworkLayerFromGenomeMap(traitRSIDsListCopy, genomeMap)
if (err != nil) { return false, false, 0, nil, 0, 0, err }
if (quantityOfLociKnown == 0){
// We can't predict anything about this trait for this genome
return true, false, 0, nil, 0, 0, nil
}
neuralNetworkObject, err := DecodeBytesToNeuralNetworkObject(predictionModelBytes)
if (err != nil) { return false, false, 0, nil, 0, 0, err }
outputLayer, err := GetNeuralNetworkRawPrediction(&neuralNetworkObject, true, neuralNetworkInput)
if (err != nil) { return false, false, 0, nil, 0, 0, err }
predictedOutcomeValue, err := GetNumericOutcomeValueFromOutputLayer(traitName, outputLayer)
if (err != nil) { return false, false, 0, nil, 0, 0, err }
modelTraitAccuracyInfoFile, err := geneticPredictionModels.GetPredictionModelNumericTraitAccuracyInfoBytes(traitName)
if (err != nil) { return false, false, 0, nil, 0, 0, err }
modelTraitAccuracyInfoMap, err := DecodeBytesToNumericTraitPredictionAccuracyInfoMap(modelTraitAccuracyInfoFile)
if (err != nil) { return false, false, 0, nil, 0, 0, err }
// We create a prediction confidence ranges map for our prediction
getPredictionConfidenceRangesMap := func()map[int]float64{
totalNumberOfTraitLoci := len(traitRSIDsList)
proportionOfLociTested := float64(quantityOfLociKnown)/float64(totalNumberOfTraitLoci)
percentageOfLociTested := int(proportionOfLociTested * 100)
proportionOfPhasedLoci := float64(quantityOfPhasedLoci)/float64(totalNumberOfTraitLoci)
percentageOfPhasedLoci := int(proportionOfPhasedLoci * 100)
// This is a value between 0 and 100 that represents the most similar confidence ranges map for this prediction
var closestPredictionConfidenceRangesMap map[int]float64
// This is a value that represents the distance our closest prediction confidence ranges map has from the current prediction
// Consider each prediction accuracy value on an (X,Y) coordinate plane
// X = Number of loci tested
// Y = Number of phased loci
closestPredictionConfidenceRangesMapDistance := float64(0)
for traitOutcomeInfo, traitPredictionConfidenceRangesMap := range modelTraitAccuracyInfoMap{
currentPercentageOfLociTested := traitOutcomeInfo.PercentageOfLociTested
currentPercentageOfPhasedLoci := traitOutcomeInfo.PercentageOfPhasedLoci
// Distance Formula for 2 coordinates (x1, y1) and (x2, y2):
// distance = √((x2 - x1)^2 + (y2 - y1)^2)
differenceInX := float64(currentPercentageOfLociTested - percentageOfLociTested)
differenceInY := float64(currentPercentageOfPhasedLoci - percentageOfPhasedLoci)
distance := math.Sqrt(math.Pow(differenceInX, 2) + math.Pow(differenceInY, 2))
if (distance == 0){
// We found the exact prediction confidence ranges map
return traitPredictionConfidenceRangesMap
}
if (closestPredictionConfidenceRangesMap == nil || distance < closestPredictionConfidenceRangesMapDistance){
closestPredictionConfidenceRangesMapDistance = distance
closestPredictionConfidenceRangesMap = traitPredictionConfidenceRangesMap
}
}
return closestPredictionConfidenceRangesMap
}
predictionConfidenceRangesMap := getPredictionConfidenceRangesMap()
return true, true, predictedOutcomeValue, predictionConfidenceRangesMap, quantityOfLociKnown, quantityOfPhasedLoci, nil
}
//Outputs:
// -[]float32: Input layer for neural network
// -int: Quantity of known loci
// -int: Quantity of phased loci
// -error
func createInputNeuralNetworkLayerFromGenomeMap(rsidsList []int64, genomeMap map[int64]locusValue.LocusValue)([]float32, int, int, error){
// In the inputLayer, each locus value is represented by 3 neurons:
// 1. LocusExists/LocusIsPhased
// -0 = Locus value is unknown
// -0.5 = Locus Is known, phase is unknown
// -1 = Locus Is Known, phase is known
// 2. Allele1 Locus Value (Value between 0-1)
// -0 = Value is unknown
// 3. Allele2 Locus Value (Value between 0-1)
// -0 = Value is unknown
//
neuralNetworkInput := make([]float32, 0)
quantityOfLociKnown := 0
quantityOfPhasedLoci := 0
for _, rsID := range rsidsList{
userLocusValue, exists := genomeMap[rsID]
if (exists == false){
neuralNetworkInput = append(neuralNetworkInput, 0, 0, 0)
continue
}
quantityOfLociKnown += 1
locusAllele1 := userLocusValue.Base1Value
locusAllele2 := userLocusValue.Base2Value
locusIsPhased := userLocusValue.LocusIsPhased
getNeuron1 := func()float32{
if (locusIsPhased == false){
return 0.5
}
quantityOfPhasedLoci += 1
return 1
}
neuron1 := getNeuron1()
neuron2, err := convertAlleleToNeuron(locusAllele1)
if (err != nil) { return nil, 0, 0, err }
neuron3, err := convertAlleleToNeuron(locusAllele2)
if (err != nil) { return nil, 0, 0, err }
neuralNetworkInput = append(neuralNetworkInput, neuron1, neuron2, neuron3)
}
return neuralNetworkInput, quantityOfLociKnown, quantityOfLociKnown, nil
}
//Outputs:
// -int: Number of loci values that are known
// -int: Number of loci values that are known and phased
@ -732,7 +854,7 @@ func getNeuralNetworkLayerSizes(traitName string)(int, int, int, int, error){
case "Height":{
// There are 3000 input neurons
// There is 1 output neuron, representing a height value
return 3000, 2, 2, 1, nil
return 3000, 3, 2, 1, nil
}
}
@ -1289,21 +1411,16 @@ func TrainNeuralNetwork(traitName string, traitIsNumeric bool, neuralNetworkObje
err = gorgonia.Let(trainingDataExpectedOutputNode, outputTensor)
if (err != nil) { return false, err }
// for i:=0; i < 10; i++{
err = virtualMachine.RunAll()
if (err != nil) { return false, err }
err = virtualMachine.RunAll()
if (err != nil) { return false, err }
// NodesToValueGrads is a utility function that converts a Nodes to a slice of ValueGrad for the solver
valueGrads := gorgonia.NodesToValueGrads(neuralNetworkLearnables)
// NodesToValueGrads is a utility function that converts a Nodes to a slice of ValueGrad for the solver
valueGrads := gorgonia.NodesToValueGrads(neuralNetworkLearnables)
err = solver.Step(valueGrads)
if (err != nil) { return false, err }
err = solver.Step(valueGrads)
if (err != nil) { return false, err }
virtualMachine.Reset()
// }
// log.Println(cost.Value())
virtualMachine.Reset()
}
return true, nil

View file

@ -892,6 +892,78 @@ func GetOffspringDiscreteTraitRuleInfoFromGeneticAnalysis(coupleAnalysisObject g
}
//Outputs:
// -bool: Any analysis exists
// -float64: Predicted outcome (Example: Height in centimeters)
// -map[int]float64: Prediction confidence ranges map
// -Map Structure: Percentage probability of accurate prediction -> distance of range in both directions from prediction
// -int: Quantity of loci known
// -int: Quantity of phased loci
// -bool: Conflict exists (between any of these results for each genome)
// -error
func GetPersonNumericTraitInfoFromGeneticAnalysis(personAnalysisObject geneticAnalysis.PersonAnalysis, traitName string, genomeIdentifier [16]byte)(bool, float64, map[int]float64, int, int, bool, error){
personTraitsMap := personAnalysisObject.NumericTraitsMap
personTraitInfoObject, exists := personTraitsMap[traitName]
if (exists == false){
return false, 0, nil, 0, 0, false, nil
}
personTraitInfoMap := personTraitInfoObject.TraitInfoMap
conflictExists := personTraitInfoObject.ConflictExists
personGenomeTraitInfoObject, exists := personTraitInfoMap[genomeIdentifier]
if (exists == false){
return false, 0, nil, 0, 0, false, nil
}
predictedOutcome := personGenomeTraitInfoObject.PredictedOutcome
confidenceRangesMap := personGenomeTraitInfoObject.ConfidenceRangesMap
quantityOfLociKnown := personGenomeTraitInfoObject.QuantityOfLociKnown
quantityOfPhasedLoci := personGenomeTraitInfoObject.QuantityOfPhasedLoci
return true, predictedOutcome, confidenceRangesMap, quantityOfLociKnown, quantityOfPhasedLoci, conflictExists, nil
}
//Outputs:
// -bool: Analysis exists
// -float64: Average offspring outcome
// -map[int]float64: Prediction confidence ranges map
// -int: Quantity of loci known
// -int: Quantity of Parental phased loci
// -[]float64: 100 Sample offspring outcomes
// -bool: Conflict exists (Between this genome pair and other genome pairs)
// -error
func GetOffspringNumericTraitInfoFromGeneticAnalysis(coupleAnalysisObject geneticAnalysis.CoupleAnalysis, traitName string, genomePairIdentifier [32]byte)(bool, float64, map[int]float64, int, int, []float64, bool, error){
offspringTraitsMap := coupleAnalysisObject.NumericTraitsMap
traitInfoObject, exists := offspringTraitsMap[traitName]
if (exists == false){
return false, 0, nil, 0, 0, nil, false, nil
}
traitInfoMap := traitInfoObject.TraitInfoMap
conflictExists := traitInfoObject.ConflictExists
genomePairTraitInfoObject, exists := traitInfoMap[genomePairIdentifier]
if (exists == false){
return false, 0, nil, 0, 0, nil, false, nil
}
offspringAverageOutcome := genomePairTraitInfoObject.OffspringAverageOutcome
predictionConfidenceRangesMap := genomePairTraitInfoObject.PredictionConfidenceRangesMap
quantityOfLociKnown := genomePairTraitInfoObject.QuantityOfLociKnown
quantityOfParentalPhasedLoci := genomePairTraitInfoObject.QuantityOfParentalPhasedLoci
sampleOffspringOutcomesList := genomePairTraitInfoObject.SampleOffspringOutcomesList
return true, offspringAverageOutcome, predictionConfidenceRangesMap, quantityOfLociKnown, quantityOfParentalPhasedLoci, sampleOffspringOutcomesList, conflictExists, nil
}
// We use this function to verify a person genetic analysis is well formed
//TODO: Perform sanity checks on data
func VerifyPersonGeneticAnalysis(personAnalysisObject geneticAnalysis.PersonAnalysis)error{
@ -995,6 +1067,13 @@ func VerifyPersonGeneticAnalysis(personAnalysisObject geneticAnalysis.PersonAnal
if (err != nil) { return err }
}
}
} else {
for _, genomeIdentifier := range allGenomeIdentifiersList{
_, _, _, _, _, _, err := GetPersonNumericTraitInfoFromGeneticAnalysis(personAnalysisObject, traitName, genomeIdentifier)
if (err != nil) { return err }
}
}
}
@ -1111,6 +1190,14 @@ func VerifyCoupleGeneticAnalysis(coupleAnalysisObject geneticAnalysis.CoupleAnal
if (err != nil) { return err }
}
}
} else {
for _, genomePairIdentifier := range allGenomePairIdentifiersList{
_, _, _, _, _, _, _, err := GetOffspringNumericTraitInfoFromGeneticAnalysis(coupleAnalysisObject, traitName, genomePairIdentifier)
if (err != nil) { return err }
}
}
}

View file

@ -2092,3 +2092,49 @@ func Split32ByteArrayInHalf(inputArray [32]byte)([16]byte, [16]byte){
return piece1, piece2
}
// This function takes a list of ints and a target value, and returns the int in the list that is the closest to that value
// If there is a tie, the function returns the earliest item in the list of the tied elements
func GetClosestIntInList(inputList []int, targetValue int)(int, error){
if (len(inputList) == 0){
return 0, errors.New("GetClosestIntInList called with empty inputList.")
}
closestValue := 0
closestValueDistance := float64(0)
for index, element := range inputList{
if (element == targetValue){
return element, nil
}
distance := math.Abs(float64(element - targetValue))
if (index == 0 || distance < closestValueDistance){
closestValue = element
closestValueDistance = distance
}
}
return closestValue, nil
}
func CountMatchingElementsInSlice[E comparable](inputSlice []E, inputElement E)int{
counter := 0
for _, element := range inputSlice{
if (element == inputElement){
counter += 1
}
}
return counter
}

View file

@ -11,6 +11,17 @@ import _ "embed"
import "errors"
//go:embed predictionModels/EyeColorModel.gob
var predictionModel_EyeColor []byte
//go:embed predictionModels/LactoseToleranceModel.gob
var predictionModel_LactoseTolerance []byte
//go:embed predictionModels/HeightModel.gob
var predictionModel_Height []byte
//Outputs:
// -bool: Model exists
// -[]byte
@ -24,16 +35,20 @@ func GetGeneticPredictionModelBytes(traitName string)(bool, []byte){
case "Lactose Tolerance":{
return true, predictionModel_LactoseTolerance
}
case "Height":{
return true, predictionModel_Height
}
}
return false, nil
}
//go:embed predictionModels/EyeColorModel.gob
var predictionModel_EyeColor []byte
//go:embed predictionModelAccuracies/EyeColorModelAccuracy.gob
var predictionAccuracy_EyeColor []byte
//go:embed predictionModelAccuracies/LactoseToleranceModelAccuracy.gob
var predictionAccuracy_LactoseTolerance []byte
//go:embed predictionModels/LactoseToleranceModel.gob
var predictionModel_LactoseTolerance []byte
// The files returned by this function are .gob encoded geneticPrediction.DiscreteTraitPredictionAccuracyInfoMap objects
func GetPredictionModelDiscreteTraitAccuracyInfoBytes(traitName string)([]byte, error){
@ -47,12 +62,22 @@ func GetPredictionModelDiscreteTraitAccuracyInfoBytes(traitName string)([]byte,
}
}
return nil, errors.New("GetPredictionModelTraitAccuracyInfoFile called with unknown traitName: " + traitName)
return nil, errors.New("GetPredictionModelDiscreteTraitAccuracyInfoBytes called with unknown traitName: " + traitName)
}
//go:embed predictionModelAccuracies/EyeColorModelAccuracy.gob
var predictionAccuracy_EyeColor []byte
//go:embed predictionModelAccuracies/HeightModelAccuracy.gob
var predictionAccuracy_Height []byte
// The files returned by this function are .gob encoded geneticPrediction.NumericTraitPredictionAccuracyInfoMap objects
func GetPredictionModelNumericTraitAccuracyInfoBytes(traitName string)([]byte, error){
switch traitName{
case "Height":{
return predictionAccuracy_Height, nil
}
}
return nil, errors.New("GetPredictionModelNumericTraitAccuracyInfoBytes called with unknown traitName: " + traitName)
}
//go:embed predictionModelAccuracies/LactoseToleranceModelAccuracy.gob
var predictionAccuracy_LactoseTolerance []byte

View file

@ -9,7 +9,7 @@ import "seekia/internal/genetics/geneticPrediction"
func TestGeneticPredictionModels(t *testing.T){
traitNamesList := []string{"Eye Color", "Lactose Tolerance"}
traitNamesList := []string{"Eye Color", "Lactose Tolerance", "Height"}
for _, traitName := range traitNamesList{
@ -42,6 +42,21 @@ func TestGeneticPredictionModelAccuracies(t *testing.T){
t.Fatalf("DecodeBytesToDiscreteTraitPredictionAccuracyInfoMap failed: " + err.Error())
}
}
numericTraitNamesList := []string{"Height"}
for _, traitName := range numericTraitNamesList{
accuracyInfoBytes, err := geneticPredictionModels.GetPredictionModelNumericTraitAccuracyInfoBytes(traitName)
if (err != nil){
t.Fatalf("GetPredictionModelNumericTraitAccuracyInfoBytes failed: " + err.Error())
}
_, err = geneticPrediction.DecodeBytesToNumericTraitPredictionAccuracyInfoMap(accuracyInfoBytes)
if (err != nil){
t.Fatalf("DecodeBytesToNumericTraitPredictionAccuracyInfoMap failed: " + err.Error())
}
}
}

View file

@ -1168,12 +1168,24 @@ func setStartAndMonitorTrainModelPage(window fyne.Window, traitName string, prev
neuralNetworkObject, err := geneticPrediction.GetNewUntrainedNeuralNetworkObject(traitName)
if (err != nil) { return false, err }
numberOfTrainingDatas := len(trainingSetFilepathsList)
numberOfTrainingDatasString := helpers.ConvertIntToString(numberOfTrainingDatas)
// 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
// This keeps track of how far along we are in training
trainingDataIndex := 0
// This keeps track of how many examples we have trained during all rounds
quantityOfExamplesTrained := 0
// Outputs:
// -bool: User stopped training
// -bool: Another training data exists
@ -1190,9 +1202,24 @@ func setStartAndMonitorTrainModelPage(window fyne.Window, traitName string, prev
return true, false, geneticPrediction.TrainingData{}, nil
}
if (trainingDataIndex == numberOfTrainingDatas){
// We are done training.
return false, false, geneticPrediction.TrainingData{}, nil
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]
})
}
trainingDataFilepath := trainingSetFilepathsList[trainingDataIndex]
@ -1206,15 +1233,15 @@ func setStartAndMonitorTrainModelPage(window fyne.Window, traitName string, prev
trainingDataObject, err := geneticPrediction.DecodeBytesToTrainingDataObject(fileContents)
if (err != nil) { return false, false, geneticPrediction.TrainingData{}, err }
trainingDataIndex += 1
quantityOfExamplesTrained += 1
numberOfExamplesTrainedString := helpers.ConvertIntToString(trainingDataIndex + 1)
numberOfExamplesProgress := "Trained " + numberOfExamplesTrainedString + "/" + numberOfTrainingDatasString + " Examples"
quantityOfExamplesTrainedString := helpers.ConvertIntToString(quantityOfExamplesTrained)
numberOfExamplesProgress := "Trained " + quantityOfExamplesTrainedString + "/" + quantityOfTrainingDatasString + " Examples"
progressDetailsBinding.Set(numberOfExamplesProgress)
newProgressFloat64 := float64(trainingDataIndex)/float64(numberOfTrainingDatas)
newProgressFloat64 := float64(quantityOfExamplesTrained)/float64(quantityOfTrainingDatas)
err = progressPercentageBinding.Set(newProgressFloat64)
if (err != nil) { return false, false, geneticPrediction.TrainingData{}, err }
@ -1239,7 +1266,7 @@ func setStartAndMonitorTrainModelPage(window fyne.Window, traitName string, prev
}
traitIsNumeric := getTraitIsNumericBool()
processCompleted, err := geneticPrediction.TrainNeuralNetwork(traitName, traitIsNumeric, neuralNetworkObject, getNextTrainingDataFunction)
if (err != nil) { return false, err }
if (processCompleted == false){
@ -1661,9 +1688,8 @@ func setStartAndMonitorTestModelPage(window fyne.Window, traitName string, previ
// We use this map to count up the information about predictions
// We use information from this map to construct the final accuracy information map
// Map Structure: NumericTraitOutcomeInfo -> List of true outcomes
traitPredictionInfoMap := make(map[geneticPrediction.NumericTraitOutcomeInfo][]float64)
// Map Structure: NumericTraitPredictionInfo -> []float64 (List of distances for each prediction)
traitPredictionInfoMap := make(map[geneticPrediction.NumericTraitPredictionInfo][]float64)
_, testingSetFilepathsList, err := getTrainingAndTestingDataFilepathLists(traitName)
if (err != nil) { return false, nil, err }
@ -1712,17 +1738,17 @@ func setStartAndMonitorTestModelPage(window fyne.Window, traitName string, previ
trainingDataInputLayer := trainingDataObject.InputLayer
trainingDataExpectedOutputLayer := trainingDataObject.OutputLayer
predictionLayer, err := geneticPrediction.GetNeuralNetworkRawPrediction(&neuralNetworkObject, false, trainingDataInputLayer)
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)
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.")
}
if (len(trainingDataExpectedOutputLayer) != 1){
return false, nil, errors.New("Neural network training data prediction output layer length is not 1.")
}
correctOutcomeValue, err := geneticPrediction.GetNumericOutcomeValueFromOutputLayer(traitName, trainingDataExpectedOutputLayer)
if (err != nil) { return false, nil, err }
@ -1738,18 +1764,19 @@ func setStartAndMonitorTestModelPage(window fyne.Window, traitName string, previ
proportionOfPhasedLoci := float64(numberOfKnownAndPhasedLoci)/float64(numberOfKnownLoci)
percentageOfPhasedLoci := int(100*proportionOfPhasedLoci)
newNumericTraitOutcomeInfo := geneticPrediction.NumericTraitOutcomeInfo{
OutcomeValue: predictedOutcomeValue,
newNumericTraitPredictionInfo := geneticPrediction.NumericTraitPredictionInfo{
PercentageOfLociTested: percentageOfLociTested,
PercentageOfPhasedLoci: percentageOfPhasedLoci,
}
existingList, exists := traitPredictionInfoMap[newNumericTraitOutcomeInfo]
distanceFromCorrectValue := math.Abs(predictedOutcomeValue - correctOutcomeValue)
existingList, exists := traitPredictionInfoMap[newNumericTraitPredictionInfo]
if (exists == false){
traitPredictionInfoMap[newNumericTraitOutcomeInfo] = []float64{correctOutcomeValue}
traitPredictionInfoMap[newNumericTraitPredictionInfo] = []float64{distanceFromCorrectValue}
} else {
existingList = append(existingList, correctOutcomeValue)
traitPredictionInfoMap[newNumericTraitOutcomeInfo] = existingList
existingList = append(existingList, distanceFromCorrectValue)
traitPredictionInfoMap[newNumericTraitPredictionInfo] = existingList
}
exampleIndexString := helpers.ConvertIntToString(index+1)
@ -1764,72 +1791,51 @@ func setStartAndMonitorTestModelPage(window fyne.Window, traitName string, previ
// Now we construct the TraitAccuracyInfoMap
// This map stores the accuracy for each outcome
traitPredictionAccuracyInfoMap := make(map[geneticPrediction.NumericTraitOutcomeInfo]geneticPrediction.NumericTraitPredictionAccuracyRangesMap)
// This map stores the accuracy for each QuantityOfKnownLoci/QuantityOfPhasedLoci
traitPredictionAccuracyInfoMap := make(map[geneticPrediction.NumericTraitPredictionInfo]geneticPrediction.NumericTraitPredictionAccuracyRangesMap)
for traitPredictionInfo, realOutcomesList := range traitPredictionInfoMap{
for traitPredictionInfo, predictionDistancesList := range traitPredictionInfoMap{
if (len(realOutcomesList) == 0){
return false, nil, errors.New("traitPredictionInfoMap contains empty realOutcomesList.")
if (len(predictionDistancesList) == 0){
return false, nil, errors.New("traitPredictionInfoMap contains empty predictionDistancesList.")
}
// This is the predicted height value for this set of real outcomes
predictionValue := traitPredictionInfo.OutcomeValue
// Map Structure: Accuracy Percentage (AP) -> Amount needed to deviate from prediction
// for the value to be accurate (AP)% of the time
newNumericTraitPredictionAccuracyRangesMap := make(map[int]float64)
rangeDistance := float64(0)
if (len(predictionDistancesList) < 5){
// We don't have enough data to create an accuracyRanges map.
continue
}
for {
// We sort the prediction distances list in ascending order
slices.Sort(predictionDistancesList)
rangeMin := predictionValue - rangeDistance
rangeMax := predictionValue + rangeDistance
finalIndex := len(predictionDistancesList) - 1
valuesInRangeList := make([]float64, 0)
valuesOutOfRangeList := make([]float64, 0)
for index, distance := range predictionDistancesList{
for _, outcomeValue := range realOutcomesList{
proportionOfPredictionsWithinDistance := float64(index)/float64(finalIndex)
if (outcomeValue <= rangeMax && outcomeValue >= rangeMin){
valuesInRangeList = append(valuesInRangeList, outcomeValue)
} else {
valuesOutOfRangeList = append(valuesOutOfRangeList, outcomeValue)
}
percentageOfPredictionsWithinDistance := int(100 * proportionOfPredictionsWithinDistance)
if (percentageOfPredictionsWithinDistance == 0){
// 0% accuracy is not a useful metric for users
continue
}
quantityOfValuesInRange := len(valuesInRangeList)
totalQuantityOfValues := len(realOutcomesList)
proportionOfValuesInRange := float64(quantityOfValuesInRange)/float64(totalQuantityOfValues)
percentageOfValuesInRange := proportionOfValuesInRange * 100
if (percentageOfValuesInRange >= 1){
percentageOfValuesInRangeInt := int(percentageOfValuesInRange)
newNumericTraitPredictionAccuracyRangesMap[percentageOfValuesInRangeInt] = rangeDistance
}
if (quantityOfValuesInRange == totalQuantityOfValues){
newNumericTraitPredictionAccuracyRangesMap[100] = rangeDistance
break
_, exists := newNumericTraitPredictionAccuracyRangesMap[percentageOfPredictionsWithinDistance]
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
}
// Now we increase rangeDistance
// We find the distance to the next closest item in the list that isn't already in our range
nearestValueDistance := float64(0)
for index, outcomeValue := range valuesOutOfRangeList{
distance := math.Abs(predictionValue - outcomeValue)
if (index == 0 || distance < nearestValueDistance){
nearestValueDistance = distance
}
}
rangeDistance += nearestValueDistance
newNumericTraitPredictionAccuracyRangesMap[percentageOfPredictionsWithinDistance] = distance
}
traitPredictionAccuracyInfoMap[traitPredictionInfo] = newNumericTraitPredictionAccuracyRangesMap
@ -1891,8 +1897,8 @@ func setViewModelTestingDiscreteTraitResultsPage(window fyne.Window, traitName s
getResultsGrid := func()(*fyne.Container, error){
outcomeNameTitle := getItalicLabelCentered("Outcome Name")
emptyLabel1 := widget.NewLabel("")
outcomeNameTitle := getItalicLabelCentered("Outcome Name")
predictionAccuracyTitle1 := getItalicLabelCentered("Prediction Accuracy")
knownLociLabel_0to33 := getItalicLabelCentered("0-33% Known Loci")
@ -1903,7 +1909,7 @@ func setViewModelTestingDiscreteTraitResultsPage(window fyne.Window, traitName s
predictionAccuracyTitle3 := getItalicLabelCentered("Prediction Accuracy")
knownLociLabel_67to100 := getItalicLabelCentered("67-100% Known Loci")
outcomeNameColumn := container.NewVBox(outcomeNameTitle, emptyLabel1, widget.NewSeparator())
outcomeNameColumn := container.NewVBox(emptyLabel1, outcomeNameTitle, widget.NewSeparator())
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())