Added numeric traits to genetic analyses.
This commit is contained in:
parent
d769047de7
commit
45e668c05a
23 changed files with 2035 additions and 194 deletions
|
@ -6,6 +6,7 @@ Small and insignificant changes may not be included in this log.
|
||||||
|
|
||||||
## Unversioned Changes
|
## Unversioned Changes
|
||||||
|
|
||||||
|
* Added numeric traits to genetic analyses. - *Simon Sarasova*
|
||||||
* Improved Documentation.md and Future-Plans.md. - *Simon Sarasova*
|
* Improved Documentation.md and Future-Plans.md. - *Simon Sarasova*
|
||||||
* Improved Future-Plans.md. - *Simon Sarasova*
|
* Improved Future-Plans.md. - *Simon Sarasova*
|
||||||
* Added Merkle Tree Payment Proofs to Future-Plans.md. - *Simon Sarasova*
|
* Added Merkle Tree Payment Proofs to Future-Plans.md. - *Simon Sarasova*
|
||||||
|
|
|
@ -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
|
Name | Date Of First Commit | Number Of Commits
|
||||||
--- | --- | ---
|
--- | --- | ---
|
||||||
Simon Sarasova | June 13, 2023 | 274
|
Simon Sarasova | June 13, 2023 | 275
|
|
@ -93,8 +93,7 @@ func setViewCoupleGeneticAnalysisPage(window fyne.Window, person1Identifier stri
|
||||||
setViewCoupleGeneticAnalysisDiscreteTraitsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, currentPage)
|
setViewCoupleGeneticAnalysisDiscreteTraitsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, currentPage)
|
||||||
})
|
})
|
||||||
numericTraitsButton := widget.NewButton("Numeric Traits", func(){
|
numericTraitsButton := widget.NewButton("Numeric Traits", func(){
|
||||||
//TODO
|
setViewCoupleGeneticAnalysisNumericTraitsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, currentPage)
|
||||||
showUnderConstructionDialog(window)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
categoryButtonsGrid := getContainerCentered(container.NewGridWithColumns(1, generalButton, monogenicDiseasesButton, polygenicDiseasesButton, discreteTraitsButton, numericTraitsButton))
|
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){
|
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)
|
offspringStatisticsDatumsList := make([]statisticsDatum.StatisticsDatum, 0)
|
||||||
|
|
||||||
for riskScore:=0; riskScore <= 10; riskScore += 1{
|
for riskScore:=0; riskScore <= 10; riskScore += 1{
|
||||||
|
|
||||||
getOffspringCount := func()int{
|
offspringCount := helpers.CountMatchingElementsInSlice(sampleOffspringRiskScoresList, riskScore)
|
||||||
|
|
||||||
offspringCount, exists := offspringRiskScoreCountsMap[riskScore]
|
|
||||||
if (exists == false){
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return offspringCount
|
|
||||||
}
|
|
||||||
|
|
||||||
offspringCount := getOffspringCount()
|
|
||||||
|
|
||||||
riskScoreString := helpers.ConvertIntToString(riskScore)
|
riskScoreString := helpers.ConvertIntToString(riskScore)
|
||||||
offspringCountString := helpers.ConvertIntToString(offspringCount)
|
offspringCountString := helpers.ConvertIntToString(offspringCount)
|
||||||
|
@ -1917,9 +1899,8 @@ func setViewCoupleGeneticAnalysisDiscreteTraitsPage(window fyne.Window, person1N
|
||||||
traitRulesList := traitObject.RulesList
|
traitRulesList := traitObject.RulesList
|
||||||
|
|
||||||
if (len(traitLociList) == 0 && len(traitRulesList) == 0){
|
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 cannot analyze it yet
|
||||||
// We will add neural network prediction so we can predict these traits
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2133,6 +2114,18 @@ func setViewCoupleGeneticAnalysisDiscreteTraitDetailsPage(window fyne.Window, pe
|
||||||
})
|
})
|
||||||
traitNameRow := container.NewHBox(layout.NewSpacer(), traitNameLabel, traitNameText, traitNameInfoButton, layout.NewSpacer())
|
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)
|
neuralNetworkExists, _ := geneticPredictionModels.GetGeneticPredictionModelBytes(traitName)
|
||||||
|
|
||||||
emptyLabel1 := widget.NewLabel("")
|
emptyLabel1 := widget.NewLabel("")
|
||||||
|
@ -2183,14 +2176,6 @@ func setViewCoupleGeneticAnalysisDiscreteTraitDetailsPage(window fyne.Window, pe
|
||||||
pairNameColumn.Add(genomePairNameLabel)
|
pairNameColumn.Add(genomePairNameLabel)
|
||||||
viewDetailsButtonsColumn.Add(viewAnalysisDetailsButton)
|
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)
|
neuralNetworkExists, neuralNetworkAnalysisExists, offspringOutcomeProbabilitiesMap_NeuralNetwork, neuralNetworkPredictionConfidence, _, _, anyRulesExist, rulesAnalysisExists, offspringOutcomeProbabilitiesMap_Rules, _, quantityOfRulesTested, _, _, err := readGeneticAnalysis.GetOffspringDiscreteTraitInfoFromGeneticAnalysis(coupleAnalysisObject, traitName, genomePairIdentifier)
|
||||||
if (err != nil) { return err }
|
if (err != nil) { return err }
|
||||||
if (neuralNetworkExists == false && anyRulesExist == false){
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,8 +26,10 @@ import "seekia/internal/genetics/myPeople"
|
||||||
import "seekia/internal/genetics/readGeneticAnalysis"
|
import "seekia/internal/genetics/readGeneticAnalysis"
|
||||||
import "seekia/internal/helpers"
|
import "seekia/internal/helpers"
|
||||||
|
|
||||||
|
import "slices"
|
||||||
import "errors"
|
import "errors"
|
||||||
|
|
||||||
|
|
||||||
func setViewPersonGeneticAnalysisPage(window fyne.Window, personIdentifier string, analysisObject geneticAnalysis.PersonAnalysis, numberOfGenomesAnalyzed int, previousPage func()){
|
func setViewPersonGeneticAnalysisPage(window fyne.Window, personIdentifier string, analysisObject geneticAnalysis.PersonAnalysis, numberOfGenomesAnalyzed int, previousPage func()){
|
||||||
|
|
||||||
appMemory.SetMemoryEntry("CurrentViewedPage", "ViewGeneticAnalysisPage")
|
appMemory.SetMemoryEntry("CurrentViewedPage", "ViewGeneticAnalysisPage")
|
||||||
|
@ -75,8 +77,7 @@ func setViewPersonGeneticAnalysisPage(window fyne.Window, personIdentifier strin
|
||||||
setViewPersonGeneticAnalysisDiscreteTraitsPage(window, personIdentifier, analysisObject, currentPage)
|
setViewPersonGeneticAnalysisDiscreteTraitsPage(window, personIdentifier, analysisObject, currentPage)
|
||||||
})
|
})
|
||||||
numericTraitsButton := widget.NewButton("Numeric Traits", func(){
|
numericTraitsButton := widget.NewButton("Numeric Traits", func(){
|
||||||
//TODO
|
setViewPersonGeneticAnalysisNumericTraitsPage(window, personIdentifier, analysisObject, currentPage)
|
||||||
showUnderConstructionDialog(window)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
categoryButtonsGrid := getContainerCentered(container.NewGridWithColumns(1, generalButton, monogenicDiseasesButton, polygenicDiseasesButton, discreteTraitsButton, numericTraitsButton))
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3555,8 +3555,7 @@ func setViewMateProfilePage_GeneticTraits(window fyne.Window, getAnyUserProfileA
|
||||||
})
|
})
|
||||||
|
|
||||||
numericTraitsButton := widget.NewButton("Numeric Traits", func(){
|
numericTraitsButton := widget.NewButton("Numeric Traits", func(){
|
||||||
//TODO
|
setViewMateProfilePage_NumericGeneticTraits(window, "Offspring", getAnyUserProfileAttributeFunction, currentPage)
|
||||||
showUnderConstructionDialog(window)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
buttonsGrid := getContainerCentered(container.NewGridWithColumns(1, discreteTraitsButton, numericTraitsButton))
|
buttonsGrid := getContainerCentered(container.NewGridWithColumns(1, discreteTraitsButton, numericTraitsButton))
|
||||||
|
@ -4400,6 +4399,364 @@ func setViewMateProfilePage_DiscreteTraitRules(window fyne.Window, traitName str
|
||||||
setPageContent(page, window)
|
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()){
|
func setViewMateProfilePage_Diet(window fyne.Window, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){
|
||||||
|
|
||||||
title := getPageTitleCentered(translate("View Profile - Lifestyle"))
|
title := getPageTitleCentered(translate("View Profile - Lifestyle"))
|
||||||
|
|
|
@ -41,7 +41,7 @@ func CreateCoupleGeneticAnalysis(person1GenomesList []prepareRawGenomes.RawGenom
|
||||||
person1PrepareRawGenomesUpdatePercentageCompleteFunction := func(newPercentage int)error{
|
person1PrepareRawGenomesUpdatePercentageCompleteFunction := func(newPercentage int)error{
|
||||||
|
|
||||||
newPercentageCompletion, err := helpers.ScaleIntProportionally(true, newPercentage, 0, 100, 0, 25)
|
newPercentageCompletion, err := helpers.ScaleIntProportionally(true, newPercentage, 0, 100, 0, 25)
|
||||||
if (err != nil){ return err }
|
if (err != nil) { return err }
|
||||||
|
|
||||||
err = updatePercentageCompleteFunction(newPercentageCompletion)
|
err = updatePercentageCompleteFunction(newPercentageCompletion)
|
||||||
if (err != nil) { return err }
|
if (err != nil) { return err }
|
||||||
|
@ -742,6 +742,104 @@ func CreateCoupleGeneticAnalysis(person1GenomesList []prepareRawGenomes.RawGenom
|
||||||
}
|
}
|
||||||
|
|
||||||
offspringDiscreteTraitsMap[traitName] = newOffspringTraitInfoObject
|
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
|
// This function will return a list of 100 prospective offspring genomes
|
||||||
// Each genome represents an equal-probability offspring genome from both people's genomes
|
// Each genome represents an equal-probability offspring genome from both people's genomes
|
||||||
// This function takes into account the effects of genetic linkage
|
// This function takes into account the effects of genetic linkage
|
||||||
|
|
|
@ -150,6 +150,14 @@ func CreatePersonGeneticAnalysis(genomesList []prepareRawGenomes.RawGenomeWithMe
|
||||||
if (err != nil) { return false, "", err }
|
if (err != nil) { return false, "", err }
|
||||||
|
|
||||||
analysisDiscreteTraitsMap[traitName] = personTraitAnalysisObject
|
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:
|
//Outputs:
|
||||||
// -int: Base pair disease locus risk weight
|
// -int: Base pair disease locus risk weight
|
||||||
// -bool: Base pair disease locus odds ratio known
|
// -bool: Base pair disease locus odds ratio known
|
||||||
|
@ -1021,7 +1102,7 @@ func GetGenomePolygenicDiseaseLocusRiskInfo(locusRiskWeightsMap map[string]int,
|
||||||
return riskWeight, true, oddsRatio, nil
|
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)
|
// The alternative prediction method is to use Rules (see GetGenomeTraitAnalysis_Rules)
|
||||||
//Outputs:
|
//Outputs:
|
||||||
// -bool: Trait Neural network analysis available (if false, we can't predict this trait using a neural network)
|
// -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
|
// 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
|
// We use this function because each rsID has aliases, so we must sometimes check those aliases to find locus values
|
||||||
//
|
//
|
||||||
|
|
|
@ -469,15 +469,15 @@ type OffspringGenomePairNumericTraitInfo struct{
|
||||||
// predicted value's range to be accurate, X% of the time?
|
// 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
|
// 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
|
// 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
|
// 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,
|
// 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
|
// this variable will have a value of 15
|
||||||
QuantityOfParentalPhasedLoci int
|
QuantityOfParentalPhasedLoci int
|
||||||
|
|
||||||
QuantityOfLociKnown int
|
|
||||||
|
|
||||||
// A list of 100 offspring outcomes for 100 prospective offspring from the genome pair
|
// A list of 100 offspring outcomes for 100 prospective offspring from the genome pair
|
||||||
// Example: A list of heights for 100 prospective offspring
|
// Example: A list of heights for 100 prospective offspring
|
||||||
SampleOffspringOutcomesList []float64
|
SampleOffspringOutcomesList []float64
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
package geneticPrediction
|
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.
|
// Machine learning experts should chime in and offer improvements.
|
||||||
// We have to make sure that model inference remains very fast
|
// 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
|
// 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 "slices"
|
||||||
import "errors"
|
import "errors"
|
||||||
|
|
||||||
//import "log"
|
|
||||||
|
|
||||||
type NeuralNetwork struct{
|
type NeuralNetwork struct{
|
||||||
|
|
||||||
|
@ -283,13 +282,9 @@ func DecodeBytesToDiscreteTraitPredictionAccuracyInfoMap(inputBytes []byte)(Disc
|
||||||
return newDiscreteTraitPredictionAccuracyInfoMap, nil
|
return newDiscreteTraitPredictionAccuracyInfoMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type NumericTraitPredictionAccuracyInfoMap map[NumericTraitOutcomeInfo]NumericTraitPredictionAccuracyRangesMap
|
type NumericTraitPredictionAccuracyInfoMap map[NumericTraitPredictionInfo]NumericTraitPredictionAccuracyRangesMap
|
||||||
|
|
||||||
type NumericTraitOutcomeInfo struct{
|
type NumericTraitPredictionInfo struct{
|
||||||
|
|
||||||
// This is the outcome which was predicted
|
|
||||||
// Example: 150 centimeters
|
|
||||||
OutcomeValue float64
|
|
||||||
|
|
||||||
// This is a value between 0-100 which describes the percentage of the loci which were tested for the input for the prediction
|
// 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
|
PercentageOfLociTested int
|
||||||
|
@ -377,54 +372,8 @@ func GetNeuralNetworkDiscreteTraitPredictionFromGenomeMap(traitName string, geno
|
||||||
traitRSIDsListCopy := slices.Clone(traitRSIDsList)
|
traitRSIDsListCopy := slices.Clone(traitRSIDsList)
|
||||||
slices.Sort(traitRSIDsListCopy)
|
slices.Sort(traitRSIDsListCopy)
|
||||||
|
|
||||||
// In the inputLayer, each locus value is represented by 3 neurons:
|
neuralNetworkInput, quantityOfLociKnown, quantityOfPhasedLoci, err := createInputNeuralNetworkLayerFromGenomeMap(traitRSIDsListCopy, genomeMap)
|
||||||
// 1. LocusExists/LocusIsPhased
|
if (err != nil) { return false, false, "", 0, 0, 0, err }
|
||||||
// -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)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (quantityOfLociKnown == 0){
|
if (quantityOfLociKnown == 0){
|
||||||
// We can't predict anything about this trait for this genome
|
// 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
|
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:
|
//Outputs:
|
||||||
// -int: Number of loci values that are known
|
// -int: Number of loci values that are known
|
||||||
// -int: Number of loci values that are known and phased
|
// -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":{
|
case "Height":{
|
||||||
// There are 3000 input neurons
|
// There are 3000 input neurons
|
||||||
// There is 1 output neuron, representing a height value
|
// 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)
|
err = gorgonia.Let(trainingDataExpectedOutputNode, outputTensor)
|
||||||
if (err != nil) { return false, err }
|
if (err != nil) { return false, err }
|
||||||
|
|
||||||
// for i:=0; i < 10; i++{
|
err = virtualMachine.RunAll()
|
||||||
|
if (err != nil) { return false, err }
|
||||||
|
|
||||||
err = virtualMachine.RunAll()
|
// NodesToValueGrads is a utility function that converts a Nodes to a slice of ValueGrad for the solver
|
||||||
if (err != nil) { return false, err }
|
valueGrads := gorgonia.NodesToValueGrads(neuralNetworkLearnables)
|
||||||
|
|
||||||
// NodesToValueGrads is a utility function that converts a Nodes to a slice of ValueGrad for the solver
|
err = solver.Step(valueGrads)
|
||||||
valueGrads := gorgonia.NodesToValueGrads(neuralNetworkLearnables)
|
if (err != nil) { return false, err }
|
||||||
|
|
||||||
err = solver.Step(valueGrads)
|
virtualMachine.Reset()
|
||||||
if (err != nil) { return false, err }
|
|
||||||
|
|
||||||
virtualMachine.Reset()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// log.Println(cost.Value())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
|
|
|
@ -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
|
// We use this function to verify a person genetic analysis is well formed
|
||||||
//TODO: Perform sanity checks on data
|
//TODO: Perform sanity checks on data
|
||||||
func VerifyPersonGeneticAnalysis(personAnalysisObject geneticAnalysis.PersonAnalysis)error{
|
func VerifyPersonGeneticAnalysis(personAnalysisObject geneticAnalysis.PersonAnalysis)error{
|
||||||
|
@ -995,6 +1067,13 @@ func VerifyPersonGeneticAnalysis(personAnalysisObject geneticAnalysis.PersonAnal
|
||||||
if (err != nil) { return err }
|
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 }
|
if (err != nil) { return err }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
for _, genomePairIdentifier := range allGenomePairIdentifiersList{
|
||||||
|
|
||||||
|
_, _, _, _, _, _, _, err := GetOffspringNumericTraitInfoFromGeneticAnalysis(coupleAnalysisObject, traitName, genomePairIdentifier)
|
||||||
|
if (err != nil) { return err }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -2092,3 +2092,49 @@ func Split32ByteArrayInHalf(inputArray [32]byte)([16]byte, [16]byte){
|
||||||
|
|
||||||
return piece1, piece2
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,17 @@ import _ "embed"
|
||||||
|
|
||||||
import "errors"
|
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:
|
//Outputs:
|
||||||
// -bool: Model exists
|
// -bool: Model exists
|
||||||
// -[]byte
|
// -[]byte
|
||||||
|
@ -24,16 +35,20 @@ func GetGeneticPredictionModelBytes(traitName string)(bool, []byte){
|
||||||
case "Lactose Tolerance":{
|
case "Lactose Tolerance":{
|
||||||
return true, predictionModel_LactoseTolerance
|
return true, predictionModel_LactoseTolerance
|
||||||
}
|
}
|
||||||
|
case "Height":{
|
||||||
|
return true, predictionModel_Height
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed predictionModels/EyeColorModel.gob
|
//go:embed predictionModelAccuracies/EyeColorModelAccuracy.gob
|
||||||
var predictionModel_EyeColor []byte
|
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
|
// The files returned by this function are .gob encoded geneticPrediction.DiscreteTraitPredictionAccuracyInfoMap objects
|
||||||
func GetPredictionModelDiscreteTraitAccuracyInfoBytes(traitName string)([]byte, error){
|
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
|
//go:embed predictionModelAccuracies/HeightModelAccuracy.gob
|
||||||
var predictionAccuracy_EyeColor []byte
|
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
|
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import "seekia/internal/genetics/geneticPrediction"
|
||||||
|
|
||||||
func TestGeneticPredictionModels(t *testing.T){
|
func TestGeneticPredictionModels(t *testing.T){
|
||||||
|
|
||||||
traitNamesList := []string{"Eye Color", "Lactose Tolerance"}
|
traitNamesList := []string{"Eye Color", "Lactose Tolerance", "Height"}
|
||||||
|
|
||||||
for _, traitName := range traitNamesList{
|
for _, traitName := range traitNamesList{
|
||||||
|
|
||||||
|
@ -42,6 +42,21 @@ func TestGeneticPredictionModelAccuracies(t *testing.T){
|
||||||
t.Fatalf("DecodeBytesToDiscreteTraitPredictionAccuracyInfoMap failed: " + err.Error())
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1168,12 +1168,24 @@ func setStartAndMonitorTrainModelPage(window fyne.Window, traitName string, prev
|
||||||
neuralNetworkObject, err := geneticPrediction.GetNewUntrainedNeuralNetworkObject(traitName)
|
neuralNetworkObject, err := geneticPrediction.GetNewUntrainedNeuralNetworkObject(traitName)
|
||||||
if (err != nil) { return false, err }
|
if (err != nil) { return false, err }
|
||||||
|
|
||||||
numberOfTrainingDatas := len(trainingSetFilepathsList)
|
// The number of rounds of training for the training data set
|
||||||
numberOfTrainingDatasString := helpers.ConvertIntToString(numberOfTrainingDatas)
|
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
|
// This keeps track of how far along we are in training
|
||||||
trainingDataIndex := 0
|
trainingDataIndex := 0
|
||||||
|
|
||||||
|
// This keeps track of how many examples we have trained during all rounds
|
||||||
|
quantityOfExamplesTrained := 0
|
||||||
|
|
||||||
// Outputs:
|
// Outputs:
|
||||||
// -bool: User stopped training
|
// -bool: User stopped training
|
||||||
// -bool: Another training data exists
|
// -bool: Another training data exists
|
||||||
|
@ -1190,9 +1202,24 @@ func setStartAndMonitorTrainModelPage(window fyne.Window, traitName string, prev
|
||||||
return true, false, geneticPrediction.TrainingData{}, nil
|
return true, false, geneticPrediction.TrainingData{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trainingDataIndex == numberOfTrainingDatas){
|
if (trainingDataIndex == quantityOfTrainingDatasInSet){
|
||||||
// We are done training.
|
// We are done training this set
|
||||||
return false, false, geneticPrediction.TrainingData{}, nil
|
|
||||||
|
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]
|
trainingDataFilepath := trainingSetFilepathsList[trainingDataIndex]
|
||||||
|
@ -1206,15 +1233,15 @@ func setStartAndMonitorTrainModelPage(window fyne.Window, traitName string, prev
|
||||||
trainingDataObject, err := geneticPrediction.DecodeBytesToTrainingDataObject(fileContents)
|
trainingDataObject, err := geneticPrediction.DecodeBytesToTrainingDataObject(fileContents)
|
||||||
if (err != nil) { return false, false, geneticPrediction.TrainingData{}, err }
|
if (err != nil) { return false, false, geneticPrediction.TrainingData{}, err }
|
||||||
|
|
||||||
|
|
||||||
trainingDataIndex += 1
|
trainingDataIndex += 1
|
||||||
|
quantityOfExamplesTrained += 1
|
||||||
|
|
||||||
numberOfExamplesTrainedString := helpers.ConvertIntToString(trainingDataIndex + 1)
|
quantityOfExamplesTrainedString := helpers.ConvertIntToString(quantityOfExamplesTrained)
|
||||||
numberOfExamplesProgress := "Trained " + numberOfExamplesTrainedString + "/" + numberOfTrainingDatasString + " Examples"
|
numberOfExamplesProgress := "Trained " + quantityOfExamplesTrainedString + "/" + quantityOfTrainingDatasString + " Examples"
|
||||||
|
|
||||||
progressDetailsBinding.Set(numberOfExamplesProgress)
|
progressDetailsBinding.Set(numberOfExamplesProgress)
|
||||||
|
|
||||||
newProgressFloat64 := float64(trainingDataIndex)/float64(numberOfTrainingDatas)
|
newProgressFloat64 := float64(quantityOfExamplesTrained)/float64(quantityOfTrainingDatas)
|
||||||
|
|
||||||
err = progressPercentageBinding.Set(newProgressFloat64)
|
err = progressPercentageBinding.Set(newProgressFloat64)
|
||||||
if (err != nil) { return false, false, geneticPrediction.TrainingData{}, err }
|
if (err != nil) { return false, false, geneticPrediction.TrainingData{}, err }
|
||||||
|
@ -1239,7 +1266,7 @@ func setStartAndMonitorTrainModelPage(window fyne.Window, traitName string, prev
|
||||||
}
|
}
|
||||||
|
|
||||||
traitIsNumeric := getTraitIsNumericBool()
|
traitIsNumeric := getTraitIsNumericBool()
|
||||||
|
|
||||||
processCompleted, err := geneticPrediction.TrainNeuralNetwork(traitName, traitIsNumeric, neuralNetworkObject, getNextTrainingDataFunction)
|
processCompleted, err := geneticPrediction.TrainNeuralNetwork(traitName, traitIsNumeric, neuralNetworkObject, getNextTrainingDataFunction)
|
||||||
if (err != nil) { return false, err }
|
if (err != nil) { return false, err }
|
||||||
if (processCompleted == false){
|
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 this map to count up the information about predictions
|
||||||
// We use information from this map to construct the final accuracy information map
|
// We use information from this map to construct the final accuracy information map
|
||||||
// Map Structure: NumericTraitOutcomeInfo -> List of true outcomes
|
// Map Structure: NumericTraitPredictionInfo -> []float64 (List of distances for each prediction)
|
||||||
traitPredictionInfoMap := make(map[geneticPrediction.NumericTraitOutcomeInfo][]float64)
|
traitPredictionInfoMap := make(map[geneticPrediction.NumericTraitPredictionInfo][]float64)
|
||||||
|
|
||||||
|
|
||||||
_, testingSetFilepathsList, err := getTrainingAndTestingDataFilepathLists(traitName)
|
_, testingSetFilepathsList, err := getTrainingAndTestingDataFilepathLists(traitName)
|
||||||
if (err != nil) { return false, nil, err }
|
if (err != nil) { return false, nil, err }
|
||||||
|
@ -1712,17 +1738,17 @@ func setStartAndMonitorTestModelPage(window fyne.Window, traitName string, previ
|
||||||
trainingDataInputLayer := trainingDataObject.InputLayer
|
trainingDataInputLayer := trainingDataObject.InputLayer
|
||||||
trainingDataExpectedOutputLayer := trainingDataObject.OutputLayer
|
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 (err != nil) { return false, nil, err }
|
||||||
|
|
||||||
if (len(predictionLayer) != 1){
|
if (len(predictionLayer) != 1){
|
||||||
return false, nil, errors.New("Neural network numeric prediction output layer length is not 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)
|
correctOutcomeValue, err := geneticPrediction.GetNumericOutcomeValueFromOutputLayer(traitName, trainingDataExpectedOutputLayer)
|
||||||
if (err != nil) { return false, nil, err }
|
if (err != nil) { return false, nil, err }
|
||||||
|
|
||||||
|
@ -1738,18 +1764,19 @@ func setStartAndMonitorTestModelPage(window fyne.Window, traitName string, previ
|
||||||
proportionOfPhasedLoci := float64(numberOfKnownAndPhasedLoci)/float64(numberOfKnownLoci)
|
proportionOfPhasedLoci := float64(numberOfKnownAndPhasedLoci)/float64(numberOfKnownLoci)
|
||||||
percentageOfPhasedLoci := int(100*proportionOfPhasedLoci)
|
percentageOfPhasedLoci := int(100*proportionOfPhasedLoci)
|
||||||
|
|
||||||
newNumericTraitOutcomeInfo := geneticPrediction.NumericTraitOutcomeInfo{
|
newNumericTraitPredictionInfo := geneticPrediction.NumericTraitPredictionInfo{
|
||||||
OutcomeValue: predictedOutcomeValue,
|
|
||||||
PercentageOfLociTested: percentageOfLociTested,
|
PercentageOfLociTested: percentageOfLociTested,
|
||||||
PercentageOfPhasedLoci: percentageOfPhasedLoci,
|
PercentageOfPhasedLoci: percentageOfPhasedLoci,
|
||||||
}
|
}
|
||||||
|
|
||||||
existingList, exists := traitPredictionInfoMap[newNumericTraitOutcomeInfo]
|
distanceFromCorrectValue := math.Abs(predictedOutcomeValue - correctOutcomeValue)
|
||||||
|
|
||||||
|
existingList, exists := traitPredictionInfoMap[newNumericTraitPredictionInfo]
|
||||||
if (exists == false){
|
if (exists == false){
|
||||||
traitPredictionInfoMap[newNumericTraitOutcomeInfo] = []float64{correctOutcomeValue}
|
traitPredictionInfoMap[newNumericTraitPredictionInfo] = []float64{distanceFromCorrectValue}
|
||||||
} else {
|
} else {
|
||||||
existingList = append(existingList, correctOutcomeValue)
|
existingList = append(existingList, distanceFromCorrectValue)
|
||||||
traitPredictionInfoMap[newNumericTraitOutcomeInfo] = existingList
|
traitPredictionInfoMap[newNumericTraitPredictionInfo] = existingList
|
||||||
}
|
}
|
||||||
|
|
||||||
exampleIndexString := helpers.ConvertIntToString(index+1)
|
exampleIndexString := helpers.ConvertIntToString(index+1)
|
||||||
|
@ -1764,72 +1791,51 @@ func setStartAndMonitorTestModelPage(window fyne.Window, traitName string, previ
|
||||||
|
|
||||||
// Now we construct the TraitAccuracyInfoMap
|
// Now we construct the TraitAccuracyInfoMap
|
||||||
|
|
||||||
// This map stores the accuracy for each outcome
|
// This map stores the accuracy for each QuantityOfKnownLoci/QuantityOfPhasedLoci
|
||||||
traitPredictionAccuracyInfoMap := make(map[geneticPrediction.NumericTraitOutcomeInfo]geneticPrediction.NumericTraitPredictionAccuracyRangesMap)
|
traitPredictionAccuracyInfoMap := make(map[geneticPrediction.NumericTraitPredictionInfo]geneticPrediction.NumericTraitPredictionAccuracyRangesMap)
|
||||||
|
|
||||||
for traitPredictionInfo, realOutcomesList := range traitPredictionInfoMap{
|
for traitPredictionInfo, predictionDistancesList := range traitPredictionInfoMap{
|
||||||
|
|
||||||
if (len(realOutcomesList) == 0){
|
if (len(predictionDistancesList) == 0){
|
||||||
return false, nil, errors.New("traitPredictionInfoMap contains empty realOutcomesList.")
|
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
|
// Map Structure: Accuracy Percentage (AP) -> Amount needed to deviate from prediction
|
||||||
// for the value to be accurate (AP)% of the time
|
// for the value to be accurate (AP)% of the time
|
||||||
newNumericTraitPredictionAccuracyRangesMap := make(map[int]float64)
|
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
|
finalIndex := len(predictionDistancesList) - 1
|
||||||
rangeMax := predictionValue + rangeDistance
|
|
||||||
|
|
||||||
valuesInRangeList := make([]float64, 0)
|
for index, distance := range predictionDistancesList{
|
||||||
valuesOutOfRangeList := make([]float64, 0)
|
|
||||||
|
|
||||||
for _, outcomeValue := range realOutcomesList{
|
proportionOfPredictionsWithinDistance := float64(index)/float64(finalIndex)
|
||||||
|
|
||||||
if (outcomeValue <= rangeMax && outcomeValue >= rangeMin){
|
percentageOfPredictionsWithinDistance := int(100 * proportionOfPredictionsWithinDistance)
|
||||||
valuesInRangeList = append(valuesInRangeList, outcomeValue)
|
|
||||||
} else {
|
if (percentageOfPredictionsWithinDistance == 0){
|
||||||
valuesOutOfRangeList = append(valuesOutOfRangeList, outcomeValue)
|
// 0% accuracy is not a useful metric for users
|
||||||
}
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
quantityOfValuesInRange := len(valuesInRangeList)
|
_, exists := newNumericTraitPredictionAccuracyRangesMap[percentageOfPredictionsWithinDistance]
|
||||||
totalQuantityOfValues := len(realOutcomesList)
|
if (exists == true){
|
||||||
|
// There exists a value for this percentage already
|
||||||
proportionOfValuesInRange := float64(quantityOfValuesInRange)/float64(totalQuantityOfValues)
|
// This happens because we convert a float64 to an int
|
||||||
percentageOfValuesInRange := proportionOfValuesInRange * 100
|
// The existing percentage must be smaller than our current percentage
|
||||||
|
// We want to keep that smaller percentage
|
||||||
if (percentageOfValuesInRange >= 1){
|
// For example, we would rather keep the 15.1% value than the 15.8% value.
|
||||||
percentageOfValuesInRangeInt := int(percentageOfValuesInRange)
|
continue
|
||||||
|
|
||||||
newNumericTraitPredictionAccuracyRangesMap[percentageOfValuesInRangeInt] = rangeDistance
|
|
||||||
}
|
|
||||||
if (quantityOfValuesInRange == totalQuantityOfValues){
|
|
||||||
newNumericTraitPredictionAccuracyRangesMap[100] = rangeDistance
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now we increase rangeDistance
|
newNumericTraitPredictionAccuracyRangesMap[percentageOfPredictionsWithinDistance] = distance
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
traitPredictionAccuracyInfoMap[traitPredictionInfo] = newNumericTraitPredictionAccuracyRangesMap
|
traitPredictionAccuracyInfoMap[traitPredictionInfo] = newNumericTraitPredictionAccuracyRangesMap
|
||||||
|
@ -1891,8 +1897,8 @@ func setViewModelTestingDiscreteTraitResultsPage(window fyne.Window, traitName s
|
||||||
|
|
||||||
getResultsGrid := func()(*fyne.Container, error){
|
getResultsGrid := func()(*fyne.Container, error){
|
||||||
|
|
||||||
outcomeNameTitle := getItalicLabelCentered("Outcome Name")
|
|
||||||
emptyLabel1 := widget.NewLabel("")
|
emptyLabel1 := widget.NewLabel("")
|
||||||
|
outcomeNameTitle := getItalicLabelCentered("Outcome Name")
|
||||||
|
|
||||||
predictionAccuracyTitle1 := getItalicLabelCentered("Prediction Accuracy")
|
predictionAccuracyTitle1 := getItalicLabelCentered("Prediction Accuracy")
|
||||||
knownLociLabel_0to33 := getItalicLabelCentered("0-33% Known Loci")
|
knownLociLabel_0to33 := getItalicLabelCentered("0-33% Known Loci")
|
||||||
|
@ -1903,7 +1909,7 @@ func setViewModelTestingDiscreteTraitResultsPage(window fyne.Window, traitName s
|
||||||
predictionAccuracyTitle3 := getItalicLabelCentered("Prediction Accuracy")
|
predictionAccuracyTitle3 := getItalicLabelCentered("Prediction Accuracy")
|
||||||
knownLociLabel_67to100 := getItalicLabelCentered("67-100% Known Loci")
|
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_0to33 := container.NewVBox(predictionAccuracyTitle1, knownLociLabel_0to33, widget.NewSeparator())
|
||||||
predictionAccuracyColumn_34to66 := container.NewVBox(predictionAccuracyTitle2, knownLociLabel_34to66, widget.NewSeparator())
|
predictionAccuracyColumn_34to66 := container.NewVBox(predictionAccuracyTitle2, knownLociLabel_34to66, widget.NewSeparator())
|
||||||
predictionAccuracyColumn_67to100 := container.NewVBox(predictionAccuracyTitle3, knownLociLabel_67to100, widget.NewSeparator())
|
predictionAccuracyColumn_67to100 := container.NewVBox(predictionAccuracyTitle3, knownLociLabel_67to100, widget.NewSeparator())
|
||||||
|
|
Loading…
Reference in a new issue