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
|
||||
|
||||
* Added numeric traits to genetic analyses. - *Simon Sarasova*
|
||||
* Improved Documentation.md and Future-Plans.md. - *Simon Sarasova*
|
||||
* Improved Future-Plans.md. - *Simon Sarasova*
|
||||
* Added Merkle Tree Payment Proofs to Future-Plans.md. - *Simon Sarasova*
|
||||
|
|
|
@ -9,4 +9,4 @@ Many other people have written code for modules which are imported by Seekia. Th
|
|||
|
||||
Name | Date Of First Commit | Number Of Commits
|
||||
--- | --- | ---
|
||||
Simon Sarasova | June 13, 2023 | 274
|
||||
Simon Sarasova | June 13, 2023 | 275
|
|
@ -93,8 +93,7 @@ func setViewCoupleGeneticAnalysisPage(window fyne.Window, person1Identifier stri
|
|||
setViewCoupleGeneticAnalysisDiscreteTraitsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, currentPage)
|
||||
})
|
||||
numericTraitsButton := widget.NewButton("Numeric Traits", func(){
|
||||
//TODO
|
||||
showUnderConstructionDialog(window)
|
||||
setViewCoupleGeneticAnalysisNumericTraitsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, currentPage)
|
||||
})
|
||||
|
||||
categoryButtonsGrid := getContainerCentered(container.NewGridWithColumns(1, generalButton, monogenicDiseasesButton, polygenicDiseasesButton, discreteTraitsButton, numericTraitsButton))
|
||||
|
@ -1789,28 +1788,11 @@ func setViewPolygenicDiseaseSampleOffspringRiskScoresChart(window fyne.Window, d
|
|||
|
||||
getOffspringSampleRiskScoresChartImage := func()(image.Image, error){
|
||||
|
||||
// Map Structure: Risk Score -> Number of offspring with that risk score
|
||||
offspringRiskScoreCountsMap := make(map[int]int)
|
||||
|
||||
for _, offspringRiskScore := range sampleOffspringRiskScoresList{
|
||||
offspringRiskScoreCountsMap[offspringRiskScore] += 1
|
||||
}
|
||||
|
||||
//TODO: Move StatisticsDatum to its own package, because we are using it for non-user purposes, and will continue to do so
|
||||
offspringStatisticsDatumsList := make([]statisticsDatum.StatisticsDatum, 0)
|
||||
|
||||
for riskScore:=0; riskScore <= 10; riskScore += 1{
|
||||
|
||||
getOffspringCount := func()int{
|
||||
|
||||
offspringCount, exists := offspringRiskScoreCountsMap[riskScore]
|
||||
if (exists == false){
|
||||
return 0
|
||||
}
|
||||
return offspringCount
|
||||
}
|
||||
|
||||
offspringCount := getOffspringCount()
|
||||
offspringCount := helpers.CountMatchingElementsInSlice(sampleOffspringRiskScoresList, riskScore)
|
||||
|
||||
riskScoreString := helpers.ConvertIntToString(riskScore)
|
||||
offspringCountString := helpers.ConvertIntToString(offspringCount)
|
||||
|
@ -1917,9 +1899,8 @@ func setViewCoupleGeneticAnalysisDiscreteTraitsPage(window fyne.Window, person1N
|
|||
traitRulesList := traitObject.RulesList
|
||||
|
||||
if (len(traitLociList) == 0 && len(traitRulesList) == 0){
|
||||
// This trait does not have any rules
|
||||
// This trait does not have any loci or rules
|
||||
// We cannot analyze it yet
|
||||
// We will add neural network prediction so we can predict these traits
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -2133,6 +2114,18 @@ func setViewCoupleGeneticAnalysisDiscreteTraitDetailsPage(window fyne.Window, pe
|
|||
})
|
||||
traitNameRow := container.NewHBox(layout.NewSpacer(), traitNameLabel, traitNameText, traitNameInfoButton, layout.NewSpacer())
|
||||
|
||||
traitObject, err := traits.GetTraitObject(traitName)
|
||||
if (err != nil){
|
||||
setErrorEncounteredPage(window, err, previousPage)
|
||||
return
|
||||
}
|
||||
|
||||
traitIsDiscreteOrNumeric := traitObject.DiscreteOrNumeric
|
||||
if (traitIsDiscreteOrNumeric != "Discrete"){
|
||||
setErrorEncounteredPage(window, errors.New("setViewCoupleGeneticAnalysisDiscreteTraitDetailsPage called with non-discrete trait: " + traitName), previousPage)
|
||||
return
|
||||
}
|
||||
|
||||
neuralNetworkExists, _ := geneticPredictionModels.GetGeneticPredictionModelBytes(traitName)
|
||||
|
||||
emptyLabel1 := widget.NewLabel("")
|
||||
|
@ -2183,14 +2176,6 @@ func setViewCoupleGeneticAnalysisDiscreteTraitDetailsPage(window fyne.Window, pe
|
|||
pairNameColumn.Add(genomePairNameLabel)
|
||||
viewDetailsButtonsColumn.Add(viewAnalysisDetailsButton)
|
||||
|
||||
traitObject, err := traits.GetTraitObject(traitName)
|
||||
if (err != nil) { return err }
|
||||
|
||||
traitIsDiscreteOrNumeric := traitObject.DiscreteOrNumeric
|
||||
if (traitIsDiscreteOrNumeric != "Discrete"){
|
||||
return errors.New("setViewCoupleGeneticAnalysisDiscreteTraitDetailsPage called with non-discrete trait: " + traitName)
|
||||
}
|
||||
|
||||
neuralNetworkExists, neuralNetworkAnalysisExists, offspringOutcomeProbabilitiesMap_NeuralNetwork, neuralNetworkPredictionConfidence, _, _, anyRulesExist, rulesAnalysisExists, offspringOutcomeProbabilitiesMap_Rules, _, quantityOfRulesTested, _, _, err := readGeneticAnalysis.GetOffspringDiscreteTraitInfoFromGeneticAnalysis(coupleAnalysisObject, traitName, genomePairIdentifier)
|
||||
if (err != nil) { return err }
|
||||
if (neuralNetworkExists == false && anyRulesExist == false){
|
||||
|
@ -2951,5 +2936,637 @@ func setViewCoupleGeneticAnalysisDiscreteTraitRuleDetailsPage(window fyne.Window
|
|||
}
|
||||
|
||||
|
||||
func setViewCoupleGeneticAnalysisNumericTraitsPage(window fyne.Window, person1Name string, person2Name string, person1AnalysisObject geneticAnalysis.PersonAnalysis, person2AnalysisObject geneticAnalysis.PersonAnalysis, coupleAnalysisObject geneticAnalysis.CoupleAnalysis, previousPage func()){
|
||||
|
||||
currentPage := func(){setViewCoupleGeneticAnalysisNumericTraitsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, previousPage)}
|
||||
|
||||
title := getPageTitleCentered("Viewing Genetic Analysis - Numeric Traits")
|
||||
|
||||
backButton := getBackButtonCentered(previousPage)
|
||||
|
||||
description := getLabelCentered("Below is an analysis of the average numeric trait outcomes for the couple's offspring.")
|
||||
|
||||
getTraitsGrid := func()(*fyne.Container, error){
|
||||
|
||||
pair1Person1GenomeIdentifier, pair1Person2GenomeIdentifier, secondGenomePairExists, _, _, _, _, _, _, _, _, err := readGeneticAnalysis.GetMetadataFromCoupleGeneticAnalysis(coupleAnalysisObject)
|
||||
if (err != nil){ return nil, err }
|
||||
|
||||
mainGenomePairIdentifier := helpers.JoinTwo16ByteArrays(pair1Person1GenomeIdentifier, pair1Person2GenomeIdentifier)
|
||||
|
||||
emptyLabel1 := widget.NewLabel("")
|
||||
traitNameLabel := getItalicLabelCentered("Trait Name")
|
||||
|
||||
emptyLabel2 := widget.NewLabel("")
|
||||
predictedOutcomeLabel := getItalicLabelCentered("Predicted Outcome")
|
||||
|
||||
quantityOfLabel := getItalicLabelCentered("Quantity Of")
|
||||
lociKnownLabel := getItalicLabelCentered("Loci Known")
|
||||
|
||||
emptyLabel3 := widget.NewLabel("")
|
||||
confidenceRangeLabel := getItalicLabelCentered("Confidence Range")
|
||||
|
||||
emptyLabel4 := widget.NewLabel("")
|
||||
conflictExistsLabel := getItalicLabelCentered("Conflict Exists?")
|
||||
|
||||
emptyLabel5 := widget.NewLabel("")
|
||||
emptyLabel6 := widget.NewLabel("")
|
||||
|
||||
traitNameColumn := container.NewVBox(emptyLabel1, traitNameLabel, widget.NewSeparator())
|
||||
predictedOutcomeColumn := container.NewVBox(emptyLabel2, predictedOutcomeLabel, widget.NewSeparator())
|
||||
confidenceRangeColumn := container.NewVBox(emptyLabel3, confidenceRangeLabel, widget.NewSeparator())
|
||||
quantityOfLociKnownColumn := container.NewVBox(quantityOfLabel, lociKnownLabel, widget.NewSeparator())
|
||||
conflictExistsColumn := container.NewVBox(emptyLabel4, conflictExistsLabel, widget.NewSeparator())
|
||||
viewDetailsButtonsColumn := container.NewVBox(emptyLabel5, emptyLabel6, widget.NewSeparator())
|
||||
|
||||
traitObjectsList, err := traits.GetTraitObjectsList()
|
||||
if (err != nil) { return nil, err }
|
||||
|
||||
for _, traitObject := range traitObjectsList{
|
||||
|
||||
traitIsDiscreteOrNumeric := traitObject.DiscreteOrNumeric
|
||||
if (traitIsDiscreteOrNumeric != "Numeric"){
|
||||
continue
|
||||
}
|
||||
|
||||
traitLociList := traitObject.LociList
|
||||
|
||||
if (len(traitLociList) == 0){
|
||||
// This trait does not have any loci
|
||||
// We cannot analyze it yet
|
||||
continue
|
||||
}
|
||||
|
||||
traitName := traitObject.TraitName
|
||||
|
||||
neuralNetworkExists, _ := geneticPredictionModels.GetGeneticPredictionModelBytes(traitName)
|
||||
if (neuralNetworkExists == false){
|
||||
// We cannot analyze this trait
|
||||
continue
|
||||
}
|
||||
|
||||
traitNameLabel := getBoldLabelCentered(traitName)
|
||||
|
||||
analysisExists, offspringAverageOutcome, predictionConfidenceRangesMap, quantityOfLociKnown, _, _, conflictExists, err := readGeneticAnalysis.GetOffspringNumericTraitInfoFromGeneticAnalysis(coupleAnalysisObject, traitName, mainGenomePairIdentifier)
|
||||
if (err != nil) { return nil, err }
|
||||
|
||||
getPredictedOutcomeLabel := func()fyne.Widget{
|
||||
if (analysisExists == false){
|
||||
result := widget.NewLabel("Unknown")
|
||||
return result
|
||||
}
|
||||
|
||||
predictedOutcomeString := helpers.ConvertFloat64ToStringRounded(offspringAverageOutcome, 2)
|
||||
|
||||
predictedOutcomeFormatted := predictedOutcomeString + " centimeters"
|
||||
|
||||
outcomeLabel := getBoldLabel(predictedOutcomeFormatted)
|
||||
|
||||
return outcomeLabel
|
||||
}
|
||||
|
||||
predictedOutcomeLabel := getPredictedOutcomeLabel()
|
||||
|
||||
predictedOutcomeLabelCentered := getWidgetCentered(predictedOutcomeLabel)
|
||||
|
||||
getConfidenceRangeLabel := func()(fyne.Widget, error){
|
||||
if (analysisExists == false){
|
||||
|
||||
result := widget.NewLabel("Unknown")
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// This is a list of the percentage accuracies in the map
|
||||
// For example: 80% == The distance from the prediction you must travel for 80% of the predictions to be
|
||||
// accurate within that range
|
||||
confidenceRangePercentagesList := helpers.GetListOfMapKeys(predictionConfidenceRangesMap)
|
||||
|
||||
// We sort the list so the percentage is always the same upon refreshing the page
|
||||
slices.Sort(confidenceRangePercentagesList)
|
||||
|
||||
closestToEightyPercentage, err := helpers.GetClosestIntInList(confidenceRangePercentagesList, 80)
|
||||
if (err != nil) { return nil, err }
|
||||
|
||||
closestToEightyPercentageConfidenceDistance, exists := predictionConfidenceRangesMap[closestToEightyPercentage]
|
||||
if (exists == false){
|
||||
return nil, errors.New("GetListOfMapKeys returning list of elements which contains element which is not in the map.")
|
||||
}
|
||||
|
||||
closestConfidenceDistanceString := helpers.ConvertFloat64ToStringRounded(closestToEightyPercentageConfidenceDistance, 2)
|
||||
|
||||
closestToEightyPercentageString := helpers.ConvertIntToString(closestToEightyPercentage)
|
||||
|
||||
confidenceRangeLabelValueFormatted := "+/- " + closestConfidenceDistanceString + " (" + closestToEightyPercentageString + "% Confidence)"
|
||||
|
||||
confidenceRangeLabel := getBoldLabel(confidenceRangeLabelValueFormatted)
|
||||
|
||||
return confidenceRangeLabel, nil
|
||||
}
|
||||
|
||||
confidenceRangeLabel, err := getConfidenceRangeLabel()
|
||||
if (err != nil) { return nil, err }
|
||||
|
||||
confidenceRangeLabelCentered := getWidgetCentered(confidenceRangeLabel)
|
||||
|
||||
totalQuantityOfLoci := len(traitLociList)
|
||||
|
||||
quantityOfLociKnownString := helpers.ConvertIntToString(quantityOfLociKnown)
|
||||
totalQuantityOfLociString := helpers.ConvertIntToString(totalQuantityOfLoci)
|
||||
|
||||
quantityOfLociKnownFormatted := quantityOfLociKnownString + "/" + totalQuantityOfLociString
|
||||
|
||||
quantityOfLociKnownLabel := getBoldLabelCentered(quantityOfLociKnownFormatted)
|
||||
|
||||
conflictExistsString := helpers.ConvertBoolToYesOrNoString(conflictExists)
|
||||
conflictExistsLabel := getBoldLabelCentered(conflictExistsString)
|
||||
|
||||
viewDetailsButton := getWidgetCentered(widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
|
||||
setViewCoupleGeneticAnalysisNumericTraitDetailsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, traitName, currentPage)
|
||||
}))
|
||||
|
||||
traitNameColumn.Add(traitNameLabel)
|
||||
predictedOutcomeColumn.Add(predictedOutcomeLabelCentered)
|
||||
confidenceRangeColumn.Add(confidenceRangeLabelCentered)
|
||||
quantityOfLociKnownColumn.Add(quantityOfLociKnownLabel)
|
||||
conflictExistsColumn.Add(conflictExistsLabel)
|
||||
viewDetailsButtonsColumn.Add(viewDetailsButton)
|
||||
|
||||
traitNameColumn.Add(widget.NewSeparator())
|
||||
predictedOutcomeColumn.Add(widget.NewSeparator())
|
||||
confidenceRangeColumn.Add(widget.NewSeparator())
|
||||
quantityOfLociKnownColumn.Add(widget.NewSeparator())
|
||||
conflictExistsColumn.Add(widget.NewSeparator())
|
||||
viewDetailsButtonsColumn.Add(widget.NewSeparator())
|
||||
}
|
||||
|
||||
predictedOutcomeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
|
||||
//TODO
|
||||
showUnderConstructionDialog(window)
|
||||
})
|
||||
predictedOutcomeColumn.Add(predictedOutcomeHelpButton)
|
||||
|
||||
confidenceRangeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
|
||||
//TODO
|
||||
showUnderConstructionDialog(window)
|
||||
})
|
||||
|
||||
confidenceRangeColumn.Add(confidenceRangeHelpButton)
|
||||
|
||||
quantityOfLociKnownHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
|
||||
//TODO
|
||||
showUnderConstructionDialog(window)
|
||||
})
|
||||
quantityOfLociKnownColumn.Add(quantityOfLociKnownHelpButton)
|
||||
|
||||
traitsGrid := container.NewHBox(layout.NewSpacer(), traitNameColumn, predictedOutcomeColumn, confidenceRangeColumn, quantityOfLociKnownColumn)
|
||||
|
||||
if (secondGenomePairExists == true){
|
||||
|
||||
conflictExistsHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
|
||||
setCoupleGeneticAnalysisConflictExistsExplainerPage(window, currentPage)
|
||||
})
|
||||
conflictExistsColumn.Add(conflictExistsHelpButton)
|
||||
|
||||
traitsGrid.Add(conflictExistsColumn)
|
||||
}
|
||||
|
||||
traitsGrid.Add(viewDetailsButtonsColumn)
|
||||
traitsGrid.Add(layout.NewSpacer())
|
||||
|
||||
return traitsGrid, nil
|
||||
}
|
||||
|
||||
traitsGrid, err := getTraitsGrid()
|
||||
if (err != nil){
|
||||
setErrorEncounteredPage(window, err, previousPage)
|
||||
return
|
||||
}
|
||||
|
||||
page := container.NewVBox(title, backButton, widget.NewSeparator(), description, widget.NewSeparator(), traitsGrid)
|
||||
|
||||
setPageContent(page, window)
|
||||
}
|
||||
|
||||
|
||||
func setViewCoupleGeneticAnalysisNumericTraitDetailsPage(window fyne.Window, person1Name string, person2Name string, person1AnalysisObject geneticAnalysis.PersonAnalysis, person2AnalysisObject geneticAnalysis.PersonAnalysis, coupleAnalysisObject geneticAnalysis.CoupleAnalysis, traitName string, previousPage func()){
|
||||
|
||||
currentPage := func(){setViewCoupleGeneticAnalysisNumericTraitDetailsPage(window, person1Name, person2Name, person1AnalysisObject, person2AnalysisObject, coupleAnalysisObject, traitName, previousPage)}
|
||||
|
||||
title := getPageTitleCentered("Viewing Couple Analysis - " + traitName)
|
||||
|
||||
backButton := getBackButtonCentered(previousPage)
|
||||
|
||||
pair1Person1GenomeIdentifier, pair1Person2GenomeIdentifier, secondGenomePairExists, pair2Person1GenomeIdentifier, pair2Person2GenomeIdentifier, _, _, _, _, _, _, err := readGeneticAnalysis.GetMetadataFromCoupleGeneticAnalysis(coupleAnalysisObject)
|
||||
if (err != nil){
|
||||
setErrorEncounteredPage(window, err, previousPage)
|
||||
return
|
||||
}
|
||||
|
||||
getDescriptionSection := func()*fyne.Container{
|
||||
|
||||
if (secondGenomePairExists == false){
|
||||
description := getLabelCentered("Below is the trait analysis for the couple's offspring.")
|
||||
|
||||
return description
|
||||
}
|
||||
|
||||
description1 := getLabelCentered("Below is the trait analysis for the couple's offspring.")
|
||||
description2 := getLabelCentered("Each genome pair combines different genomes from each person.")
|
||||
|
||||
descriptionsSection := container.NewVBox(description1, description2)
|
||||
|
||||
return descriptionsSection
|
||||
}
|
||||
|
||||
descriptionSection := getDescriptionSection()
|
||||
|
||||
traitNameLabel := widget.NewLabel("Trait:")
|
||||
traitNameText := getBoldLabel(traitName)
|
||||
traitNameInfoButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){
|
||||
setViewTraitDetailsPage(window, traitName, currentPage)
|
||||
})
|
||||
traitNameRow := container.NewHBox(layout.NewSpacer(), traitNameLabel, traitNameText, traitNameInfoButton, layout.NewSpacer())
|
||||
|
||||
neuralNetworkExists, _ := geneticPredictionModels.GetGeneticPredictionModelBytes(traitName)
|
||||
if (neuralNetworkExists == false){
|
||||
// We cannot analyze this trait
|
||||
setErrorEncounteredPage(window, errors.New("setViewCoupleGeneticAnalysisNumericTraitDetailsPage called non-analyzable trait: " + traitName), previousPage)
|
||||
return
|
||||
}
|
||||
|
||||
traitObject, err := traits.GetTraitObject(traitName)
|
||||
if (err != nil){
|
||||
setErrorEncounteredPage(window, err, previousPage)
|
||||
return
|
||||
}
|
||||
|
||||
traitIsDiscreteOrNumeric := traitObject.DiscreteOrNumeric
|
||||
if (traitIsDiscreteOrNumeric != "Numeric"){
|
||||
setErrorEncounteredPage(window, errors.New("setViewCoupleGeneticAnalysisNumericTraitDetailsPage called with non-numeric trait: " + traitName), previousPage)
|
||||
return
|
||||
}
|
||||
|
||||
traitLociList := traitObject.LociList
|
||||
|
||||
emptyLabel1 := widget.NewLabel("")
|
||||
emptyLabel2 := widget.NewLabel("")
|
||||
|
||||
emptyLabel3 := widget.NewLabel("")
|
||||
genomePairLabel := getItalicLabelCentered("Genome Pair")
|
||||
|
||||
emptyLabel4 := widget.NewLabel("")
|
||||
predictedOutcomeLabel := getItalicLabelCentered("Predicted Outcome")
|
||||
|
||||
emptyLabel5 := widget.NewLabel("")
|
||||
confidenceRangeLabel := getItalicLabelCentered("Confidence Range")
|
||||
|
||||
quantityOfLabel := getItalicLabelCentered("Quantity Of")
|
||||
lociKnownLabel := getItalicLabelCentered("Loci Known")
|
||||
|
||||
emptyLabel6 := widget.NewLabel("")
|
||||
emptyLabel7 := widget.NewLabel("")
|
||||
|
||||
emptyLabel8 := widget.NewLabel("")
|
||||
emptyLabel9 := widget.NewLabel("")
|
||||
|
||||
viewGenomePairButtonsColumn := container.NewVBox(emptyLabel1, emptyLabel2, widget.NewSeparator())
|
||||
pairNameColumn := container.NewVBox(emptyLabel3, genomePairLabel, widget.NewSeparator())
|
||||
predictedOutcomeColumn := container.NewVBox(emptyLabel4, predictedOutcomeLabel, widget.NewSeparator())
|
||||
predictionConfidenceRangeColumn := container.NewVBox(emptyLabel5, confidenceRangeLabel, widget.NewSeparator())
|
||||
quantityOfLociKnownColumn := container.NewVBox(quantityOfLabel, lociKnownLabel, widget.NewSeparator())
|
||||
viewSampleOffspringButtonsColumn := container.NewVBox(emptyLabel6, emptyLabel7, widget.NewSeparator())
|
||||
viewDetailsButtonsColumn := container.NewVBox(emptyLabel8, emptyLabel9, widget.NewSeparator())
|
||||
|
||||
addGenomePairRow := func(genomePairName string, person1GenomeIdentifier [16]byte, person2GenomeIdentifier [16]byte)error{
|
||||
|
||||
genomePairIdentifier := helpers.JoinTwo16ByteArrays(person1GenomeIdentifier, person2GenomeIdentifier)
|
||||
|
||||
genomePairNameLabel := getBoldLabelCentered(genomePairName)
|
||||
|
||||
viewGenomePairButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){
|
||||
//TODO
|
||||
showUnderConstructionDialog(window)
|
||||
})
|
||||
|
||||
analysisExists, offspringAverageOutcome, predictionConfidenceRangesMap, quantityOfLociKnown, _, sampleOffspringOutcomesList, _, err := readGeneticAnalysis.GetOffspringNumericTraitInfoFromGeneticAnalysis(coupleAnalysisObject, traitName, genomePairIdentifier)
|
||||
if (err != nil) { return err }
|
||||
|
||||
getPredictedOutcomeLabel := func()fyne.Widget{
|
||||
if (analysisExists == false){
|
||||
unknownLabel := widget.NewLabel("Unknown")
|
||||
return unknownLabel
|
||||
}
|
||||
|
||||
offspringAverageOutcomeString := helpers.ConvertFloat64ToStringRounded(offspringAverageOutcome, 2)
|
||||
|
||||
offspringAverageOutcomeFormatted := offspringAverageOutcomeString + " centimeters"
|
||||
|
||||
predictedOutcomeLabel := getBoldLabel(offspringAverageOutcomeFormatted)
|
||||
|
||||
return predictedOutcomeLabel
|
||||
}
|
||||
|
||||
predictedOutcomeLabel := getPredictedOutcomeLabel()
|
||||
|
||||
predictedOutcomeLabelCentered := getWidgetCentered(predictedOutcomeLabel)
|
||||
|
||||
getConfidenceRangeLabel := func()(fyne.Widget, error){
|
||||
if (analysisExists == false){
|
||||
|
||||
result := widget.NewLabel("Unknown")
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// This is a list of the percentage accuracies in the map
|
||||
// For example: 80% == The distance from the prediction you must travel for 80% of the predictions to be
|
||||
// accurate within that range
|
||||
confidenceRangePercentagesList := helpers.GetListOfMapKeys(predictionConfidenceRangesMap)
|
||||
|
||||
// We sort the list so the percentage is always the same upon refreshing the page
|
||||
slices.Sort(confidenceRangePercentagesList)
|
||||
|
||||
closestToEightyPercentage, err := helpers.GetClosestIntInList(confidenceRangePercentagesList, 80)
|
||||
if (err != nil) { return nil, err }
|
||||
|
||||
closestToEightyPercentageConfidenceDistance, exists := predictionConfidenceRangesMap[closestToEightyPercentage]
|
||||
if (exists == false){
|
||||
return nil, errors.New("GetListOfMapKeys returning list of elements which contains element which is not in the map.")
|
||||
}
|
||||
|
||||
closestConfidenceDistanceString := helpers.ConvertFloat64ToStringRounded(closestToEightyPercentageConfidenceDistance, 2)
|
||||
|
||||
closestToEightyPercentageString := helpers.ConvertIntToString(closestToEightyPercentage)
|
||||
|
||||
confidenceRangeLabelValueFormatted := "+/- " + closestConfidenceDistanceString + " (" + closestToEightyPercentageString + "% Confidence)"
|
||||
|
||||
confidenceRangeLabel := getBoldLabel(confidenceRangeLabelValueFormatted)
|
||||
|
||||
return confidenceRangeLabel, nil
|
||||
}
|
||||
|
||||
confidenceRangeLabel, err := getConfidenceRangeLabel()
|
||||
if (err != nil) { return err }
|
||||
|
||||
confidenceRangeLabelCentered := getWidgetCentered(confidenceRangeLabel)
|
||||
|
||||
totalQuantityOfLoci := len(traitLociList)
|
||||
|
||||
quantityOfLociKnownString := helpers.ConvertIntToString(quantityOfLociKnown)
|
||||
totalQuantityOfLociString := helpers.ConvertIntToString(totalQuantityOfLoci)
|
||||
|
||||
quantityOfLociKnownFormatted := quantityOfLociKnownString + "/" + totalQuantityOfLociString
|
||||
|
||||
quantityOfLociKnownLabel := getBoldLabelCentered(quantityOfLociKnownFormatted)
|
||||
|
||||
viewSampleOffspringsButton := widget.NewButtonWithIcon("", theme.InfoIcon(), func(){
|
||||
setViewNumericTraitSampleOffspringRiskScoresChart(window, traitName, sampleOffspringOutcomesList, quantityOfLociKnown, currentPage)
|
||||
})
|
||||
|
||||
viewAnalysisDetailsButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
|
||||
//TODO
|
||||
showUnderConstructionDialog(window)
|
||||
})
|
||||
|
||||
viewGenomePairButtonsColumn.Add(viewGenomePairButton)
|
||||
pairNameColumn.Add(genomePairNameLabel)
|
||||
predictedOutcomeColumn.Add(predictedOutcomeLabelCentered)
|
||||
predictionConfidenceRangeColumn.Add(confidenceRangeLabelCentered)
|
||||
quantityOfLociKnownColumn.Add(quantityOfLociKnownLabel)
|
||||
viewSampleOffspringButtonsColumn.Add(viewSampleOffspringsButton)
|
||||
viewDetailsButtonsColumn.Add(viewAnalysisDetailsButton)
|
||||
|
||||
viewGenomePairButtonsColumn.Add(widget.NewSeparator())
|
||||
pairNameColumn.Add(widget.NewSeparator())
|
||||
predictedOutcomeColumn.Add(widget.NewSeparator())
|
||||
predictionConfidenceRangeColumn.Add(widget.NewSeparator())
|
||||
quantityOfLociKnownColumn.Add(widget.NewSeparator())
|
||||
viewSampleOffspringButtonsColumn.Add(widget.NewSeparator())
|
||||
viewDetailsButtonsColumn.Add(widget.NewSeparator())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
err = addGenomePairRow("Pair 1", pair1Person1GenomeIdentifier, pair1Person2GenomeIdentifier)
|
||||
if (err != nil) {
|
||||
setErrorEncounteredPage(window, err, previousPage)
|
||||
return
|
||||
}
|
||||
|
||||
if (secondGenomePairExists == true){
|
||||
err := addGenomePairRow("Pair 2", pair2Person1GenomeIdentifier, pair2Person2GenomeIdentifier)
|
||||
if (err != nil) {
|
||||
setErrorEncounteredPage(window, err, previousPage)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
predictedOutcomeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
|
||||
//TODO
|
||||
showUnderConstructionDialog(window)
|
||||
})
|
||||
predictedOutcomeColumn.Add(predictedOutcomeHelpButton)
|
||||
|
||||
confidenceRangeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
|
||||
//TODO
|
||||
showUnderConstructionDialog(window)
|
||||
})
|
||||
|
||||
predictionConfidenceRangeColumn.Add(confidenceRangeHelpButton)
|
||||
|
||||
quantityOfLociKnownHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
|
||||
//TODO
|
||||
showUnderConstructionDialog(window)
|
||||
})
|
||||
quantityOfLociKnownColumn.Add(quantityOfLociKnownHelpButton)
|
||||
|
||||
genomesContainer := container.NewHBox(layout.NewSpacer(), viewGenomePairButtonsColumn, pairNameColumn, predictedOutcomeColumn, predictionConfidenceRangeColumn, quantityOfLociKnownColumn, viewSampleOffspringButtonsColumn, viewDetailsButtonsColumn, layout.NewSpacer())
|
||||
|
||||
page := container.NewVBox(title, backButton, widget.NewSeparator(), descriptionSection, widget.NewSeparator(), traitNameRow, widget.NewSeparator(), genomesContainer)
|
||||
|
||||
setPageContent(page, window)
|
||||
}
|
||||
|
||||
|
||||
// This is a page that shows the user 100 sample offspring trait outcomes on a bar chart
|
||||
// This helps users to visualize the standard deviation of their offspring's trait outcomes with this user
|
||||
func setViewNumericTraitSampleOffspringRiskScoresChart(window fyne.Window, traitName string, sampleOffspringOutcomesList []float64, quantityOfLociKnown int, previousPage func()){
|
||||
|
||||
currentPage := func(){setViewNumericTraitSampleOffspringRiskScoresChart(window, traitName, sampleOffspringOutcomesList, quantityOfLociKnown, previousPage)}
|
||||
|
||||
title := getPageTitleCentered("Viewing Sample Offspring Outcomes Chart")
|
||||
|
||||
backButton := getBackButtonCentered(previousPage)
|
||||
|
||||
description := widget.NewLabel("Below is a chart of 100 sample offspring trait outcomes for this disease.")
|
||||
descriptionHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
|
||||
//TODO
|
||||
showUnderConstructionDialog(window)
|
||||
})
|
||||
descriptionRow := container.NewHBox(layout.NewSpacer(), description, descriptionHelpButton, layout.NewSpacer())
|
||||
|
||||
traitNameTitle := widget.NewLabel("Trait Name:")
|
||||
traitNameLabel := getBoldLabel(traitName)
|
||||
traitNameRow := container.NewHBox(layout.NewSpacer(), traitNameTitle, traitNameLabel, layout.NewSpacer())
|
||||
|
||||
if (len(sampleOffspringOutcomesList) == 0){
|
||||
description2 := getBoldLabelCentered("There is no offspring information available for this trait.")
|
||||
description3 := getBoldLabelCentered("This is because there were no trait loci for which both prospective parents had information.")
|
||||
|
||||
page := container.NewVBox(title, backButton, widget.NewSeparator(), descriptionRow, widget.NewSeparator(), traitNameRow, widget.NewSeparator(), description2, description3)
|
||||
|
||||
setPageContent(page, window)
|
||||
return
|
||||
}
|
||||
|
||||
traitObject, err := traits.GetTraitObject(traitName)
|
||||
if (err != nil){
|
||||
setErrorEncounteredPage(window, err, previousPage)
|
||||
return
|
||||
}
|
||||
|
||||
traitLociList := traitObject.LociList
|
||||
|
||||
totalNumberOfLoci := len(traitLociList)
|
||||
totalNumberOfLociString := helpers.ConvertIntToString(totalNumberOfLoci)
|
||||
|
||||
numberOfLociKnownTitle := widget.NewLabel("Quantity Of Loci Known:")
|
||||
numberOfLociKnownString := helpers.ConvertIntToString(quantityOfLociKnown)
|
||||
numberOfLociKnownLabel := getBoldLabel(numberOfLociKnownString + "/" + totalNumberOfLociString)
|
||||
|
||||
lociKnownHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
|
||||
//TODO
|
||||
showUnderConstructionDialog(window)
|
||||
})
|
||||
|
||||
quantityOfLociKnownRow := container.NewHBox(layout.NewSpacer(), numberOfLociKnownTitle, numberOfLociKnownLabel, lociKnownHelpButton, layout.NewSpacer())
|
||||
|
||||
getOffspringSampleOutcomesChartImage := func()(image.Image, error){
|
||||
|
||||
if (len(sampleOffspringOutcomesList) != 100){
|
||||
return nil, errors.New("setViewNumericTraitSampleOffspringRiskScoresChart called with offspringOutcomesList that is not 100 items in length.")
|
||||
}
|
||||
|
||||
// We sort the list in ascending order
|
||||
slices.Sort(sampleOffspringOutcomesList)
|
||||
|
||||
getOffspringStatisticsDatumsList := func()([]statisticsDatum.StatisticsDatum){
|
||||
|
||||
offspringStatisticsDatumsList := make([]statisticsDatum.StatisticsDatum, 0)
|
||||
|
||||
smallestItem := sampleOffspringOutcomesList[0]
|
||||
largestItem := sampleOffspringOutcomesList[99]
|
||||
|
||||
if (smallestItem == largestItem){
|
||||
|
||||
// We can't split the values into groups
|
||||
// Every offspring has the same value
|
||||
|
||||
onlyValueString := helpers.ConvertFloat64ToStringRounded(smallestItem, 2)
|
||||
|
||||
onlyValueFormatted := onlyValueString + " centimeters"
|
||||
|
||||
newStatisticsDatum := statisticsDatum.StatisticsDatum{
|
||||
Label: onlyValueString,
|
||||
LabelFormatted: onlyValueFormatted,
|
||||
Value: float64(100),
|
||||
ValueFormatted: "100",
|
||||
}
|
||||
|
||||
offspringStatisticsDatumsList = append(offspringStatisticsDatumsList, newStatisticsDatum)
|
||||
|
||||
return offspringStatisticsDatumsList
|
||||
}
|
||||
|
||||
// We split all outcomes into ten groups
|
||||
|
||||
sizeOfEachGroup := (largestItem-smallestItem)/10
|
||||
|
||||
index := float64(0)
|
||||
|
||||
for {
|
||||
|
||||
getGroupUpperBound := func()float64{
|
||||
|
||||
if (len(offspringStatisticsDatumsList) == 9){
|
||||
// We are adding the last datum
|
||||
return largestItem
|
||||
}
|
||||
|
||||
groupUpperBound := index + sizeOfEachGroup
|
||||
|
||||
return groupUpperBound
|
||||
}
|
||||
|
||||
groupUpperBound := getGroupUpperBound()
|
||||
|
||||
offspringInGroupCount := 0
|
||||
|
||||
for _, outcomeValue := range sampleOffspringOutcomesList{
|
||||
|
||||
if (outcomeValue >= index && outcomeValue < groupUpperBound){
|
||||
offspringInGroupCount += 1
|
||||
}
|
||||
}
|
||||
|
||||
groupLowerBoundString := helpers.ConvertFloat64ToStringRounded(index, 1)
|
||||
groupUpperBoundString := helpers.ConvertFloat64ToStringRounded(groupUpperBound, 1)
|
||||
|
||||
groupDescription := groupLowerBoundString + "-" + groupUpperBoundString + " centimeters"
|
||||
|
||||
offspringCountString := helpers.ConvertIntToString(offspringInGroupCount)
|
||||
|
||||
newStatisticsDatum := statisticsDatum.StatisticsDatum{
|
||||
Label: groupDescription,
|
||||
LabelFormatted: groupDescription,
|
||||
Value: float64(offspringInGroupCount),
|
||||
ValueFormatted: offspringCountString,
|
||||
}
|
||||
|
||||
offspringStatisticsDatumsList = append(offspringStatisticsDatumsList, newStatisticsDatum)
|
||||
|
||||
if (len(offspringStatisticsDatumsList) == 10){
|
||||
break
|
||||
}
|
||||
|
||||
index += sizeOfEachGroup
|
||||
}
|
||||
|
||||
return offspringStatisticsDatumsList
|
||||
}
|
||||
|
||||
offspringStatisticsDatumsList := getOffspringStatisticsDatumsList()
|
||||
|
||||
chartTitle := traitName + ": 100 Prospective Offspring Values"
|
||||
|
||||
formatYAxisValuesFunction := func(inputTraitValue float64)(string, error){
|
||||
|
||||
result := helpers.ConvertFloat64ToStringRounded(inputTraitValue, 2)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
offspringsChart, err := createCharts.CreateBarChart(chartTitle, offspringStatisticsDatumsList, formatYAxisValuesFunction, true, " Offspring")
|
||||
if (err != nil) { return nil, err }
|
||||
|
||||
return offspringsChart, nil
|
||||
}
|
||||
|
||||
offspringOutcomesChartImage, err := getOffspringSampleOutcomesChartImage()
|
||||
if (err != nil){
|
||||
setErrorEncounteredPage(window, err, previousPage)
|
||||
return
|
||||
}
|
||||
|
||||
viewChartFullscreenButton := getWidgetCentered(widget.NewButtonWithIcon("View Fullscreen", theme.ZoomInIcon(), func(){
|
||||
setViewFullpageImagePage(window, offspringOutcomesChartImage, currentPage)
|
||||
}))
|
||||
|
||||
pageHeader := container.NewVBox(title, backButton, widget.NewSeparator(), descriptionRow, widget.NewSeparator(), traitNameRow, widget.NewSeparator(), quantityOfLociKnownRow, widget.NewSeparator())
|
||||
|
||||
chartFyneImage := canvas.NewImageFromImage(offspringOutcomesChartImage)
|
||||
chartFyneImage.FillMode = canvas.ImageFillContain
|
||||
|
||||
page := container.NewBorder(pageHeader, viewChartFullscreenButton, nil, nil, chartFyneImage)
|
||||
|
||||
setPageContent(page, window)
|
||||
}
|
||||
|
||||
|
|
|
@ -26,8 +26,10 @@ import "seekia/internal/genetics/myPeople"
|
|||
import "seekia/internal/genetics/readGeneticAnalysis"
|
||||
import "seekia/internal/helpers"
|
||||
|
||||
import "slices"
|
||||
import "errors"
|
||||
|
||||
|
||||
func setViewPersonGeneticAnalysisPage(window fyne.Window, personIdentifier string, analysisObject geneticAnalysis.PersonAnalysis, numberOfGenomesAnalyzed int, previousPage func()){
|
||||
|
||||
appMemory.SetMemoryEntry("CurrentViewedPage", "ViewGeneticAnalysisPage")
|
||||
|
@ -75,8 +77,7 @@ func setViewPersonGeneticAnalysisPage(window fyne.Window, personIdentifier strin
|
|||
setViewPersonGeneticAnalysisDiscreteTraitsPage(window, personIdentifier, analysisObject, currentPage)
|
||||
})
|
||||
numericTraitsButton := widget.NewButton("Numeric Traits", func(){
|
||||
//TODO
|
||||
showUnderConstructionDialog(window)
|
||||
setViewPersonGeneticAnalysisNumericTraitsPage(window, personIdentifier, analysisObject, currentPage)
|
||||
})
|
||||
|
||||
categoryButtonsGrid := getContainerCentered(container.NewGridWithColumns(1, generalButton, monogenicDiseasesButton, polygenicDiseasesButton, discreteTraitsButton, numericTraitsButton))
|
||||
|
@ -2672,3 +2673,230 @@ func setViewPersonGeneticAnalysisDiscreteTraitRuleDetailsPage(window fyne.Window
|
|||
}
|
||||
|
||||
|
||||
func setViewPersonGeneticAnalysisNumericTraitsPage(window fyne.Window, personIdentifier string, analysisObject geneticAnalysis.PersonAnalysis, previousPage func()){
|
||||
|
||||
currentPage := func(){setViewPersonGeneticAnalysisNumericTraitsPage(window, personIdentifier, analysisObject, previousPage)}
|
||||
|
||||
title := getPageTitleCentered("Viewing Genetic Analysis - Numeric Traits")
|
||||
|
||||
backButton := getBackButtonCentered(previousPage)
|
||||
|
||||
description := getLabelCentered("Below is an analysis of the numeric traits for this person's genome.")
|
||||
|
||||
getTraitsContainer := func()(*fyne.Container, error){
|
||||
|
||||
allRawGenomeIdentifiersList, multipleGenomesExist, onlyExcludeConflictsGenomeIdentifier, _, _, err := readGeneticAnalysis.GetMetadataFromPersonGeneticAnalysis(analysisObject)
|
||||
if (err != nil){ return nil, err }
|
||||
|
||||
// Outputs:
|
||||
// -[16]byte: Main genome identifier (Is either the combined Only exclude conflicts genome or the only genome)
|
||||
// -error
|
||||
getMainGenomeIdentifier := func()([16]byte, error){
|
||||
|
||||
if (multipleGenomesExist == true){
|
||||
return onlyExcludeConflictsGenomeIdentifier, nil
|
||||
}
|
||||
// Only 1 genome exists
|
||||
|
||||
genomeIdentifier := allRawGenomeIdentifiersList[0]
|
||||
|
||||
return genomeIdentifier, nil
|
||||
}
|
||||
|
||||
mainGenomeIdentifier, err := getMainGenomeIdentifier()
|
||||
if (err != nil){ return nil, err }
|
||||
|
||||
emptyLabel1 := widget.NewLabel("")
|
||||
traitNameLabel := getItalicLabelCentered("Trait Name")
|
||||
|
||||
emptyLabel2 := widget.NewLabel("")
|
||||
predictedOutcomeLabel := getItalicLabelCentered("Predicted Outcome")
|
||||
|
||||
quantityOfLabel := getItalicLabelCentered("Quantity Of")
|
||||
lociKnownLabel := getItalicLabelCentered("Loci Known")
|
||||
|
||||
emptyLabel3 := widget.NewLabel("")
|
||||
confidenceRangeLabel := getItalicLabelCentered("Confidence Range")
|
||||
|
||||
emptyLabel4 := widget.NewLabel("")
|
||||
conflictExistsLabel := getItalicLabelCentered("Conflict Exists?")
|
||||
|
||||
emptyLabel5 := widget.NewLabel("")
|
||||
emptyLabel6 := widget.NewLabel("")
|
||||
|
||||
traitNameColumn := container.NewVBox(emptyLabel1, traitNameLabel, widget.NewSeparator())
|
||||
predictedOutcomeColumn := container.NewVBox(emptyLabel2, predictedOutcomeLabel, widget.NewSeparator())
|
||||
confidenceRangeColumn := container.NewVBox(emptyLabel3, confidenceRangeLabel, widget.NewSeparator())
|
||||
quantityOfLociKnownColumn := container.NewVBox(quantityOfLabel, lociKnownLabel, widget.NewSeparator())
|
||||
conflictExistsColumn := container.NewVBox(emptyLabel4, conflictExistsLabel, widget.NewSeparator())
|
||||
viewButtonsColumn := container.NewVBox(emptyLabel5, emptyLabel6, widget.NewSeparator())
|
||||
|
||||
traitObjectsList, err := traits.GetTraitObjectsList()
|
||||
if (err != nil) { return nil, err }
|
||||
|
||||
for _, traitObject := range traitObjectsList{
|
||||
|
||||
traitIsDiscreteOrNumeric := traitObject.DiscreteOrNumeric
|
||||
if (traitIsDiscreteOrNumeric != "Numeric"){
|
||||
continue
|
||||
}
|
||||
|
||||
traitName := traitObject.TraitName
|
||||
traitLociList := traitObject.LociList
|
||||
|
||||
if (len(traitLociList) == 0){
|
||||
// This trait does not have any loci to analyze
|
||||
// We cannot analyze it yet
|
||||
continue
|
||||
}
|
||||
|
||||
traitNameText := getBoldLabelCentered(traitName)
|
||||
|
||||
neuralNetworkExists, _ := geneticPredictionModels.GetGeneticPredictionModelBytes(traitName)
|
||||
if (neuralNetworkExists == false){
|
||||
// This trait has no neural network
|
||||
// We cannot analyze it
|
||||
continue
|
||||
}
|
||||
|
||||
analysisExists, predictedOutcome, confidenceRangesMap, quantityOfLociKnown, _, conflictExists, err := readGeneticAnalysis.GetPersonNumericTraitInfoFromGeneticAnalysis(analysisObject, traitName, mainGenomeIdentifier)
|
||||
if (err != nil) { return nil, err }
|
||||
|
||||
getPredictionLabel := func()fyne.Widget{
|
||||
|
||||
if (analysisExists == false){
|
||||
result := getItalicLabel("Unknown")
|
||||
return result
|
||||
}
|
||||
|
||||
predictedOutcomeString := helpers.ConvertFloat64ToStringRounded(predictedOutcome, 2)
|
||||
|
||||
//TODO: Fix units
|
||||
result := getBoldLabel(predictedOutcomeString + " centimeters")
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
predictionLabel := getPredictionLabel()
|
||||
|
||||
predictionLabelCentered := getWidgetCentered(predictionLabel)
|
||||
|
||||
getConfidenceRangeLabel := func()(fyne.Widget, error){
|
||||
|
||||
if (analysisExists == false){
|
||||
unknownLabel := widget.NewLabel("Unknown")
|
||||
return unknownLabel, nil
|
||||
}
|
||||
|
||||
// This is a list of the percentage accuracies in the map
|
||||
// For example: 80% == The distance from the prediction you must travel for 80% of the predictions to be
|
||||
// accurate within that range
|
||||
confidenceRangePercentagesList := helpers.GetListOfMapKeys(confidenceRangesMap)
|
||||
|
||||
// We sort the list so the percentage is always the same upon refreshing the page
|
||||
slices.Sort(confidenceRangePercentagesList)
|
||||
|
||||
closestToEightyPercentage, err := helpers.GetClosestIntInList(confidenceRangePercentagesList, 80)
|
||||
if (err != nil) { return nil, err }
|
||||
|
||||
closestToEightyPercentageConfidenceDistance, exists := confidenceRangesMap[closestToEightyPercentage]
|
||||
if (exists == false){
|
||||
return nil, errors.New("GetListOfMapKeys returning list of elements which contains element which is not in the map.")
|
||||
}
|
||||
|
||||
closestConfidenceDistanceString := helpers.ConvertFloat64ToStringRounded(closestToEightyPercentageConfidenceDistance, 2)
|
||||
|
||||
closestToEightyPercentageString := helpers.ConvertIntToString(closestToEightyPercentage)
|
||||
|
||||
confidenceRangeLabelValueFormatted := "+/- " + closestConfidenceDistanceString + " (" + closestToEightyPercentageString + "% Confidence)"
|
||||
|
||||
confidenceRangeLabel := getBoldLabel(confidenceRangeLabelValueFormatted)
|
||||
|
||||
return confidenceRangeLabel, nil
|
||||
}
|
||||
|
||||
confidenceRangeLabel, err := getConfidenceRangeLabel()
|
||||
if (err != nil) { return nil, err }
|
||||
|
||||
confidenceRangeLabelCentered := getWidgetCentered(confidenceRangeLabel)
|
||||
|
||||
totalQuantityOfLoci := len(traitLociList)
|
||||
|
||||
quantityOfLociKnownString := helpers.ConvertIntToString(quantityOfLociKnown)
|
||||
totalQuantityOfLociString := helpers.ConvertIntToString(totalQuantityOfLoci)
|
||||
|
||||
quantityOfLociKnownFormatted := quantityOfLociKnownString + "/" + totalQuantityOfLociString
|
||||
|
||||
quantityOfLociKnownLabel := getBoldLabelCentered(quantityOfLociKnownFormatted)
|
||||
|
||||
conflictExistsString := helpers.ConvertBoolToYesOrNoString(conflictExists)
|
||||
conflictExistsLabel := getBoldLabelCentered(conflictExistsString)
|
||||
|
||||
viewDetailsButton := getWidgetCentered(widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
|
||||
showUnderConstructionDialog(window)
|
||||
//TODO
|
||||
}))
|
||||
|
||||
traitNameColumn.Add(traitNameText)
|
||||
predictedOutcomeColumn.Add(predictionLabelCentered)
|
||||
confidenceRangeColumn.Add(confidenceRangeLabelCentered)
|
||||
quantityOfLociKnownColumn.Add(quantityOfLociKnownLabel)
|
||||
conflictExistsColumn.Add(conflictExistsLabel)
|
||||
viewButtonsColumn.Add(viewDetailsButton)
|
||||
|
||||
traitNameColumn.Add(widget.NewSeparator())
|
||||
predictedOutcomeColumn.Add(widget.NewSeparator())
|
||||
confidenceRangeColumn.Add(widget.NewSeparator())
|
||||
quantityOfLociKnownColumn.Add(widget.NewSeparator())
|
||||
conflictExistsColumn.Add(widget.NewSeparator())
|
||||
viewButtonsColumn.Add(widget.NewSeparator())
|
||||
}
|
||||
|
||||
predictedOutcomeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
|
||||
//TODO
|
||||
showUnderConstructionDialog(window)
|
||||
})
|
||||
predictedOutcomeColumn.Add(predictedOutcomeHelpButton)
|
||||
|
||||
confidenceRangeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
|
||||
//TODO
|
||||
showUnderConstructionDialog(window)
|
||||
})
|
||||
confidenceRangeColumn.Add(confidenceRangeHelpButton)
|
||||
|
||||
quantityOfLociKnownHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
|
||||
//TODO
|
||||
showUnderConstructionDialog(window)
|
||||
})
|
||||
quantityOfLociKnownColumn.Add(quantityOfLociKnownHelpButton)
|
||||
|
||||
traitsContainer := container.NewHBox(layout.NewSpacer(), traitNameColumn, predictedOutcomeColumn, confidenceRangeColumn, quantityOfLociKnownColumn)
|
||||
|
||||
if (multipleGenomesExist == true){
|
||||
|
||||
conflictExistsHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
|
||||
setPersonGeneticAnalysisConflictExistsExplainerPage(window, currentPage)
|
||||
})
|
||||
|
||||
conflictExistsColumn.Add(conflictExistsHelpButton)
|
||||
traitsContainer.Add(conflictExistsColumn)
|
||||
}
|
||||
|
||||
traitsContainer.Add(viewButtonsColumn)
|
||||
traitsContainer.Add(layout.NewSpacer())
|
||||
|
||||
return traitsContainer, nil
|
||||
}
|
||||
|
||||
traitsContainer, err := getTraitsContainer()
|
||||
if (err != nil){
|
||||
setErrorEncounteredPage(window, err, previousPage)
|
||||
return
|
||||
}
|
||||
|
||||
page := container.NewVBox(title, backButton, widget.NewSeparator(), description, widget.NewSeparator(), traitsContainer)
|
||||
|
||||
setPageContent(page, window)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -3555,8 +3555,7 @@ func setViewMateProfilePage_GeneticTraits(window fyne.Window, getAnyUserProfileA
|
|||
})
|
||||
|
||||
numericTraitsButton := widget.NewButton("Numeric Traits", func(){
|
||||
//TODO
|
||||
showUnderConstructionDialog(window)
|
||||
setViewMateProfilePage_NumericGeneticTraits(window, "Offspring", getAnyUserProfileAttributeFunction, currentPage)
|
||||
})
|
||||
|
||||
buttonsGrid := getContainerCentered(container.NewGridWithColumns(1, discreteTraitsButton, numericTraitsButton))
|
||||
|
@ -4400,6 +4399,364 @@ func setViewMateProfilePage_DiscreteTraitRules(window fyne.Window, traitName str
|
|||
setPageContent(page, window)
|
||||
}
|
||||
|
||||
|
||||
func setViewMateProfilePage_NumericGeneticTraits(window fyne.Window, userOrOffspring string, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){
|
||||
|
||||
if (userOrOffspring != "User" && userOrOffspring != "Offspring"){
|
||||
setErrorEncounteredPage(window, errors.New("setViewMateProfilePage_NumericGeneticTraits called with invalid userOrOffspring: " + userOrOffspring), previousPage)
|
||||
return
|
||||
}
|
||||
|
||||
if (userOrOffspring == "Offspring"){
|
||||
setLoadingScreen(window, "View Profile - Physical", "Computing Genetic Analysis...")
|
||||
}
|
||||
|
||||
//currentPage := func(){setViewMateProfilePage_NumericGeneticTraits(window, userOrOffspring, getAnyUserProfileAttributeFunction, previousPage)}
|
||||
|
||||
title := getPageTitleCentered(translate("View Profile - Physical"))
|
||||
|
||||
backButton := getBackButtonCentered(previousPage)
|
||||
|
||||
subtitle := getPageSubtitleCentered(translate("Numeric Genetic Traits"))
|
||||
|
||||
description1 := getLabelCentered("Below is the numeric genetic trait analysis for this user.")
|
||||
description2 := getLabelCentered("You can choose to view the analysis of the user or an offspring between you and the user.")
|
||||
description3 := getLabelCentered("You must link your genome person in the Build Profile menu to see offspring information.")
|
||||
|
||||
handleSelectButton := func(newUserOrOffspring string){
|
||||
if (userOrOffspring == newUserOrOffspring){
|
||||
return
|
||||
}
|
||||
setViewMateProfilePage_NumericGeneticTraits(window, newUserOrOffspring, getAnyUserProfileAttributeFunction, previousPage)
|
||||
}
|
||||
|
||||
userOrOffspringSelector := widget.NewSelect([]string{"User", "Offspring"}, handleSelectButton)
|
||||
userOrOffspringSelector.Selected = userOrOffspring
|
||||
|
||||
userOrOffspringSelectorCentered := getWidgetCentered(userOrOffspringSelector)
|
||||
|
||||
getTraitsInfoGrid := func()(*fyne.Container, error){
|
||||
|
||||
myPersonChosen, myGenomesExist, myAnalysisIsReady, myAnalysisObject, myGenomeIdentifier, _, err := myChosenAnalysis.GetMyChosenMateGeneticAnalysis()
|
||||
if (err != nil) { return nil, err }
|
||||
|
||||
//Outputs:
|
||||
// -map[int64]locusValue.LocusValue
|
||||
// -error
|
||||
getMyGenomeLocusValuesMap := func()(map[int64]locusValue.LocusValue, error){
|
||||
|
||||
if (myPersonChosen == false || myGenomesExist == false || myAnalysisIsReady == false){
|
||||
emptyMap := make(map[int64]locusValue.LocusValue)
|
||||
return emptyMap, nil
|
||||
}
|
||||
|
||||
_, _, _, _, myGenomesMap, err := readGeneticAnalysis.GetMetadataFromPersonGeneticAnalysis(myAnalysisObject)
|
||||
if (err != nil) { return nil, err }
|
||||
|
||||
myGenomeLocusValuesMap, exists := myGenomesMap[myGenomeIdentifier]
|
||||
if (exists == false){
|
||||
return nil, errors.New("GetMyChosenMateGeneticAnalysis returning analysis which is missing genome with myGenomeIdentifier.")
|
||||
}
|
||||
|
||||
return myGenomeLocusValuesMap, nil
|
||||
}
|
||||
|
||||
myGenomeLocusValuesMap, err := getMyGenomeLocusValuesMap()
|
||||
if (err != nil) { return nil, err }
|
||||
|
||||
|
||||
emptyLabel1 := widget.NewLabel("")
|
||||
traitNameLabel := getItalicLabelCentered("Trait Name")
|
||||
|
||||
emptyLabel2 := widget.NewLabel("")
|
||||
userPredictedOutcomeTitle := getItalicLabelCentered("User Predicted Outcome")
|
||||
|
||||
emptyLabel3 := widget.NewLabel("")
|
||||
offspringPredictedOutcomeTitle := getItalicLabelCentered("Offspring Predicted Outcome")
|
||||
|
||||
predictionTitle := getItalicLabelCentered("Prediction")
|
||||
confidenceRangeTitle := getItalicLabelCentered("Confidence Range")
|
||||
|
||||
quantityOfLabel1 := getItalicLabelCentered("Quantity Of")
|
||||
lociKnownLabel := getItalicLabelCentered("Loci Known")
|
||||
|
||||
emptyLabel4 := widget.NewLabel("")
|
||||
emptyLabel5 := widget.NewLabel("")
|
||||
|
||||
traitNameColumn := container.NewVBox(emptyLabel1, traitNameLabel, widget.NewSeparator())
|
||||
userPredictedOutcomeColumn := container.NewVBox(emptyLabel2, userPredictedOutcomeTitle, widget.NewSeparator())
|
||||
|
||||
offspringPredictedOutcomeColumn := container.NewVBox(emptyLabel3, offspringPredictedOutcomeTitle, widget.NewSeparator())
|
||||
|
||||
predictionConfidenceRangeColumn := container.NewVBox(predictionTitle, confidenceRangeTitle, widget.NewSeparator())
|
||||
quantityOfLociKnownColumn := container.NewVBox(quantityOfLabel1, lociKnownLabel, widget.NewSeparator())
|
||||
viewTraitDetailsButtonsColumn := container.NewVBox(emptyLabel4, emptyLabel5, widget.NewSeparator())
|
||||
|
||||
traitObjectsList, err := traits.GetTraitObjectsList()
|
||||
if (err != nil) { return nil, err }
|
||||
|
||||
for _, traitObject := range traitObjectsList{
|
||||
|
||||
traitName := traitObject.TraitName
|
||||
traitIsDiscreteOrNumeric := traitObject.DiscreteOrNumeric
|
||||
if (traitIsDiscreteOrNumeric != "Numeric"){
|
||||
continue
|
||||
}
|
||||
|
||||
traitLociList := traitObject.LociList
|
||||
|
||||
numberOfTraitLoci := len(traitLociList)
|
||||
|
||||
if (numberOfTraitLoci == 0){
|
||||
// We are not able to analyze these traits
|
||||
continue
|
||||
}
|
||||
|
||||
traitNeuralNetworkExists, _ := geneticPredictionModels.GetGeneticPredictionModelBytes(traitName)
|
||||
if (traitNeuralNetworkExists == false){
|
||||
// We are not able to analyze these traits yet
|
||||
continue
|
||||
}
|
||||
|
||||
traitNameText := getBoldLabelCentered(translate(traitName))
|
||||
|
||||
// We construct the user's trait locus values map
|
||||
// Map Structure: Locus rsID -> locusValue.LocusValue
|
||||
userTraitLocusValuesMap := make(map[int64]locusValue.LocusValue)
|
||||
|
||||
for _, rsID := range traitLociList{
|
||||
|
||||
rsIDString := helpers.ConvertInt64ToString(rsID)
|
||||
|
||||
userLocusValueAttributeName := "LocusValue_rs" + rsIDString
|
||||
|
||||
userLocusValueIsKnown, _, userLocusValue, err := getAnyUserProfileAttributeFunction(userLocusValueAttributeName)
|
||||
if (err != nil) { return nil, err }
|
||||
if (userLocusValueIsKnown == false){
|
||||
continue
|
||||
}
|
||||
|
||||
userLocusBase1, userLocusBase2, semicolonFound := strings.Cut(userLocusValue, ";")
|
||||
if (semicolonFound == false){
|
||||
return nil, errors.New("Database corrupt: Contains profile with invalid " + userLocusValueAttributeName + " value: " + userLocusValue)
|
||||
}
|
||||
|
||||
userLocusIsPhasedAttributeName := "LocusIsPhased_rs" + rsIDString
|
||||
|
||||
userLocusIsPhasedExists, _, userLocusIsPhasedString, err := getAnyUserProfileAttributeFunction(userLocusIsPhasedAttributeName)
|
||||
if (err != nil) { return nil, err }
|
||||
if (userLocusIsPhasedExists == false){
|
||||
return nil, errors.New("Database corrupt: Contains profile with locusValue but not locusIsPhased status for locus: " + rsIDString)
|
||||
}
|
||||
|
||||
userLocusIsPhased, err := helpers.ConvertYesOrNoStringToBool(userLocusIsPhasedString)
|
||||
if (err != nil) { return nil, err }
|
||||
|
||||
userLocusValueObject := locusValue.LocusValue{
|
||||
Base1Value: userLocusBase1,
|
||||
Base2Value: userLocusBase2,
|
||||
LocusIsPhased: userLocusIsPhased,
|
||||
}
|
||||
|
||||
userTraitLocusValuesMap[rsID] = userLocusValueObject
|
||||
}
|
||||
|
||||
//Outputs:
|
||||
// -bool: Analysis results exist
|
||||
// -float64: Predicted outcome
|
||||
// -map[int]float64: Prediction confidence ranges map
|
||||
// -int: Quantity of known loci
|
||||
// -error
|
||||
getAnalysisResults := func()(bool, float64, map[int]float64, int, error){
|
||||
|
||||
if (userOrOffspring == "User"){
|
||||
|
||||
traitNeuralNetworkExists, anyLocusValuesAreKnown, predictedOutcome, predictionAccuracyRangesMap, quantityOfLociKnown, _, err := createPersonGeneticAnalysis.GetGenomeNumericTraitAnalysis(traitObject, userTraitLocusValuesMap, true)
|
||||
if (err != nil) { return false, 0, nil, 0, err }
|
||||
if (traitNeuralNetworkExists == false){
|
||||
return false, 0, nil, 0, errors.New("GetGenomeNumericTraitAnalysis claims neural network doesn't exist for trait, but we already checked.")
|
||||
}
|
||||
|
||||
return anyLocusValuesAreKnown, predictedOutcome, predictionAccuracyRangesMap, quantityOfLociKnown, nil
|
||||
} else {
|
||||
|
||||
// userOrOffspring == "Offspring"
|
||||
|
||||
neuralNetworkExists, anyLociKnown, predictedOutcome, predictionAccuracyRangesMap, _, quantityOfLociKnown, _, err := createCoupleGeneticAnalysis.GetOffspringNumericTraitInfo(traitObject, userTraitLocusValuesMap, myGenomeLocusValuesMap)
|
||||
if (err != nil) { return false, 0, nil, 0, err }
|
||||
if (neuralNetworkExists == false){
|
||||
return false, 0, nil, 0, errors.New("GetOffspringTraitInfo_NeuralNetwork claiming that neural network doesn't exist when we already checked.")
|
||||
}
|
||||
|
||||
return anyLociKnown, predictedOutcome, predictionAccuracyRangesMap, quantityOfLociKnown, nil
|
||||
}
|
||||
}
|
||||
|
||||
analysisExists, predictedOutcome, predictionConfidenceRangesMap, quantityOfLociKnown, err := getAnalysisResults()
|
||||
if (err != nil) { return nil, err }
|
||||
|
||||
getPredictedOutcomeLabel := func()fyne.Widget{
|
||||
|
||||
if (analysisExists == false){
|
||||
unknownLabel := widget.NewLabel("Unknown")
|
||||
return unknownLabel
|
||||
}
|
||||
|
||||
predictedOutcomeString := helpers.ConvertFloat64ToStringRounded(predictedOutcome, 2)
|
||||
|
||||
//TODO: Retrieve units from traits package
|
||||
predictedOutcomeFormatted := predictedOutcomeString + " centimeters"
|
||||
|
||||
predictedOutcomeLabel := getBoldLabel(predictedOutcomeFormatted)
|
||||
|
||||
return predictedOutcomeLabel
|
||||
}
|
||||
|
||||
predictedOutcomeLabel := getPredictedOutcomeLabel()
|
||||
|
||||
predictedOutcomeLabelCentered := getWidgetCentered(predictedOutcomeLabel)
|
||||
|
||||
getConfidenceRangeLabel := func()(fyne.Widget, error){
|
||||
if (analysisExists == false){
|
||||
|
||||
result := widget.NewLabel("Unknown")
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// This is a list of the percentage accuracies in the map
|
||||
// For example: 80% == The distance from the prediction you must travel for 80% of the predictions to be
|
||||
// accurate within that range
|
||||
confidenceRangePercentagesList := helpers.GetListOfMapKeys(predictionConfidenceRangesMap)
|
||||
|
||||
// We sort the list so the percentage is always the same upon refreshing the page
|
||||
slices.Sort(confidenceRangePercentagesList)
|
||||
|
||||
closestToEightyPercentage, err := helpers.GetClosestIntInList(confidenceRangePercentagesList, 80)
|
||||
if (err != nil) { return nil, err }
|
||||
|
||||
closestToEightyPercentageConfidenceDistance, exists := predictionConfidenceRangesMap[closestToEightyPercentage]
|
||||
if (exists == false){
|
||||
return nil, errors.New("GetListOfMapKeys returning list of elements which contains element which is not in the map.")
|
||||
}
|
||||
|
||||
closestConfidenceDistanceString := helpers.ConvertFloat64ToStringRounded(closestToEightyPercentageConfidenceDistance, 2)
|
||||
|
||||
closestToEightyPercentageString := helpers.ConvertIntToString(closestToEightyPercentage)
|
||||
|
||||
confidenceRangeLabelValueFormatted := "+/- " + closestConfidenceDistanceString + " (" + closestToEightyPercentageString + "% Confidence)"
|
||||
|
||||
confidenceRangeLabel := getBoldLabel(confidenceRangeLabelValueFormatted)
|
||||
|
||||
return confidenceRangeLabel, nil
|
||||
}
|
||||
|
||||
confidenceRangeLabel, err := getConfidenceRangeLabel()
|
||||
if (err != nil) { return nil, err }
|
||||
|
||||
confidenceRangeLabelCentered := getWidgetCentered(confidenceRangeLabel)
|
||||
|
||||
totalNumberOfLociString := helpers.ConvertIntToString(numberOfTraitLoci)
|
||||
|
||||
quantityOfLociKnownString := helpers.ConvertIntToString(quantityOfLociKnown)
|
||||
|
||||
quantityOfLociKnownFormatted := quantityOfLociKnownString + "/" + totalNumberOfLociString
|
||||
|
||||
quantityOfLociKnownLabel := getBoldLabelCentered(quantityOfLociKnownFormatted)
|
||||
|
||||
viewTraitDetailsButton := widget.NewButtonWithIcon("", theme.VisibilityIcon(), func(){
|
||||
//TODO
|
||||
showUnderConstructionDialog(window)
|
||||
})
|
||||
|
||||
traitNameColumn.Add(traitNameText)
|
||||
|
||||
if (userOrOffspring == "User"){
|
||||
userPredictedOutcomeColumn.Add(predictedOutcomeLabelCentered)
|
||||
} else {
|
||||
offspringPredictedOutcomeColumn.Add(predictedOutcomeLabelCentered)
|
||||
}
|
||||
|
||||
predictionConfidenceRangeColumn.Add(confidenceRangeLabelCentered)
|
||||
quantityOfLociKnownColumn.Add(quantityOfLociKnownLabel)
|
||||
viewTraitDetailsButtonsColumn.Add(viewTraitDetailsButton)
|
||||
|
||||
traitNameColumn.Add(widget.NewSeparator())
|
||||
userPredictedOutcomeColumn.Add(widget.NewSeparator())
|
||||
offspringPredictedOutcomeColumn.Add(widget.NewSeparator())
|
||||
predictionConfidenceRangeColumn.Add(widget.NewSeparator())
|
||||
quantityOfLociKnownColumn.Add(widget.NewSeparator())
|
||||
viewTraitDetailsButtonsColumn.Add(widget.NewSeparator())
|
||||
}
|
||||
|
||||
if (userOrOffspring == "User"){
|
||||
|
||||
predictedOutcomeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
|
||||
|
||||
//TODO
|
||||
showUnderConstructionDialog(window)
|
||||
})
|
||||
|
||||
userPredictedOutcomeColumn.Add(predictedOutcomeHelpButton)
|
||||
|
||||
} else {
|
||||
// userOrOffspring == "Offspring"
|
||||
|
||||
predictedOutcomeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
|
||||
|
||||
//TODO
|
||||
showUnderConstructionDialog(window)
|
||||
})
|
||||
|
||||
offspringPredictedOutcomeColumn.Add(predictedOutcomeHelpButton)
|
||||
}
|
||||
|
||||
predictionConfidenceRangeHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
|
||||
|
||||
//TODO
|
||||
showUnderConstructionDialog(window)
|
||||
})
|
||||
|
||||
predictionConfidenceRangeColumn.Add(predictionConfidenceRangeHelpButton)
|
||||
|
||||
quantityOfLociKnownHelpButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){
|
||||
//TODO
|
||||
showUnderConstructionDialog(window)
|
||||
})
|
||||
|
||||
quantityOfLociKnownColumn.Add(quantityOfLociKnownHelpButton)
|
||||
|
||||
traitsInfoGrid := container.NewHBox(layout.NewSpacer(), traitNameColumn)
|
||||
|
||||
if (userOrOffspring == "User"){
|
||||
|
||||
traitsInfoGrid.Add(userPredictedOutcomeColumn)
|
||||
|
||||
} else {
|
||||
|
||||
// userOrOffspring == "Offspring"
|
||||
|
||||
traitsInfoGrid.Add(offspringPredictedOutcomeColumn)
|
||||
}
|
||||
|
||||
traitsInfoGrid.Add(predictionConfidenceRangeColumn)
|
||||
traitsInfoGrid.Add(quantityOfLociKnownColumn)
|
||||
traitsInfoGrid.Add(viewTraitDetailsButtonsColumn)
|
||||
traitsInfoGrid.Add(layout.NewSpacer())
|
||||
|
||||
return traitsInfoGrid, nil
|
||||
}
|
||||
|
||||
traitsInfoGrid, err := getTraitsInfoGrid()
|
||||
if (err != nil){
|
||||
setErrorEncounteredPage(window, err, previousPage)
|
||||
return
|
||||
}
|
||||
|
||||
page := container.NewVBox(title, backButton, widget.NewSeparator(), subtitle, widget.NewSeparator(), description1, description2, description3, widget.NewSeparator(), userOrOffspringSelectorCentered, widget.NewSeparator(), traitsInfoGrid)
|
||||
|
||||
setPageContent(page, window)
|
||||
}
|
||||
|
||||
func setViewMateProfilePage_Diet(window fyne.Window, getAnyUserProfileAttributeFunction func(string)(bool, int, string, error), previousPage func()){
|
||||
|
||||
title := getPageTitleCentered(translate("View Profile - Lifestyle"))
|
||||
|
|
|
@ -41,7 +41,7 @@ func CreateCoupleGeneticAnalysis(person1GenomesList []prepareRawGenomes.RawGenom
|
|||
person1PrepareRawGenomesUpdatePercentageCompleteFunction := func(newPercentage int)error{
|
||||
|
||||
newPercentageCompletion, err := helpers.ScaleIntProportionally(true, newPercentage, 0, 100, 0, 25)
|
||||
if (err != nil){ return err }
|
||||
if (err != nil) { return err }
|
||||
|
||||
err = updatePercentageCompleteFunction(newPercentageCompletion)
|
||||
if (err != nil) { return err }
|
||||
|
@ -742,6 +742,104 @@ func CreateCoupleGeneticAnalysis(person1GenomesList []prepareRawGenomes.RawGenom
|
|||
}
|
||||
|
||||
offspringDiscreteTraitsMap[traitName] = newOffspringTraitInfoObject
|
||||
|
||||
} else {
|
||||
|
||||
// traitIsDiscreteOrNumeric == "Numeric"
|
||||
|
||||
// This map stores the trait info for each genome pair
|
||||
// Map Structure: Genome Pair Identifier -> OffspringGenomePairNumericTraitInfo
|
||||
offspringTraitInfoMap := make(map[[32]byte]geneticAnalysis.OffspringGenomePairNumericTraitInfo)
|
||||
|
||||
// This will add the offspring trait information for the provided genome pair to the offspringTraitInfoMap
|
||||
addGenomePairTraitInfoToOffspringMap := func(person1GenomeIdentifier [16]byte, person2GenomeIdentifier [16]byte)error{
|
||||
|
||||
person1LocusValuesMap, exists := person1GenomesMap[person1GenomeIdentifier]
|
||||
if (exists == false){
|
||||
return errors.New("addGenomePairTraitInfoToOffspringMap called with unknown person1GenomeIdentifier.")
|
||||
}
|
||||
|
||||
person2LocusValuesMap, exists := person2GenomesMap[person2GenomeIdentifier]
|
||||
if (exists == false){
|
||||
return errors.New("addGenomePairTraitInfoToOffspringMap called with unknown person2GenomeIdentifier.")
|
||||
}
|
||||
|
||||
neuralNetworkExists, neuralNetworkAnalysisExists, averageOutcome, predictionConfidenceRangesMap, sampleOffspringOutcomesList, quantityOfLociTested, quantityOfParentalPhasedLoci, err := GetOffspringNumericTraitInfo(traitObject, person1LocusValuesMap, person2LocusValuesMap)
|
||||
if (err != nil) { return err }
|
||||
if (neuralNetworkExists == false){
|
||||
// Predictions are not possible for this trait
|
||||
return nil
|
||||
}
|
||||
if (neuralNetworkAnalysisExists == false){
|
||||
// No locations for this trait exists in which both user's genomes contain information
|
||||
return nil
|
||||
}
|
||||
|
||||
newOffspringGenomePairTraitInfo := geneticAnalysis.OffspringGenomePairNumericTraitInfo{
|
||||
OffspringAverageOutcome: averageOutcome,
|
||||
PredictionConfidenceRangesMap: predictionConfidenceRangesMap,
|
||||
QuantityOfParentalPhasedLoci: quantityOfParentalPhasedLoci,
|
||||
QuantityOfLociKnown: quantityOfLociTested,
|
||||
SampleOffspringOutcomesList: sampleOffspringOutcomesList,
|
||||
}
|
||||
|
||||
genomePairIdentifier := helpers.JoinTwo16ByteArrays(person1GenomeIdentifier, person2GenomeIdentifier)
|
||||
|
||||
offspringTraitInfoMap[genomePairIdentifier] = newOffspringGenomePairTraitInfo
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
err = addGenomePairTraitInfoToOffspringMap(pair1Person1GenomeIdentifier, pair1Person2GenomeIdentifier)
|
||||
if (err != nil) { return false, "", err }
|
||||
|
||||
if (genomePair2Exists == true){
|
||||
|
||||
err := addGenomePairTraitInfoToOffspringMap(pair2Person1GenomeIdentifier, pair2Person2GenomeIdentifier)
|
||||
if (err != nil) { return false, "", err }
|
||||
}
|
||||
|
||||
newOffspringTraitInfoObject := geneticAnalysis.OffspringNumericTraitInfo{
|
||||
TraitInfoMap: offspringTraitInfoMap,
|
||||
}
|
||||
|
||||
if (len(offspringTraitInfoMap) >= 2){
|
||||
|
||||
// We check for conflicts
|
||||
// Conflicts are only possible if two genome pairs exist with information about the trait
|
||||
|
||||
checkIfConflictExists := func()(bool, error){
|
||||
|
||||
// We check for conflicts between each genome pair's outcome scores and trait rules maps
|
||||
|
||||
genomePairTraitInfoObject := geneticAnalysis.OffspringGenomePairNumericTraitInfo{}
|
||||
|
||||
firstItemReached := false
|
||||
|
||||
for _, currentGenomePairTraitInfoObject := range offspringTraitInfoMap{
|
||||
|
||||
if (firstItemReached == false){
|
||||
genomePairTraitInfoObject = currentGenomePairTraitInfoObject
|
||||
firstItemReached = true
|
||||
continue
|
||||
}
|
||||
|
||||
areEqual := reflect.DeepEqual(genomePairTraitInfoObject, currentGenomePairTraitInfoObject)
|
||||
if (areEqual == false){
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
conflictExists, err := checkIfConflictExists()
|
||||
if (err != nil) { return false, "", err }
|
||||
|
||||
newOffspringTraitInfoObject.ConflictExists = conflictExists
|
||||
}
|
||||
|
||||
offspringNumericTraitsMap[traitName] = newOffspringTraitInfoObject
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1323,6 +1421,110 @@ func GetOffspringDiscreteTraitInfo_Rules(traitObject traits.Trait, person1LocusV
|
|||
}
|
||||
|
||||
|
||||
|
||||
//Outputs:
|
||||
// -bool: A neural network exists for this trait
|
||||
// -bool: Analysis exists (at least 1 locus exists for this analysis from both people's genomes
|
||||
// -float64: Average outcome for offspring
|
||||
// -map[int]float64: Prediction accuracy ranges map
|
||||
// -Map Structure: Probability prediction is accurate (X) -> Distance from predictoin that must be travelled in both directions to
|
||||
// create a range in which the true value will fall into, X% of the time
|
||||
// -[]float64: A list of 100 offspring outcomes
|
||||
// -int: Quantity of loci known
|
||||
// -int: Quantity of parental phased loci
|
||||
// -error
|
||||
func GetOffspringNumericTraitInfo(traitObject traits.Trait, person1LocusValuesMap map[int64]locusValue.LocusValue, person2LocusValuesMap map[int64]locusValue.LocusValue)(bool, bool, float64, map[int]float64, []float64, int, int, error){
|
||||
|
||||
traitName := traitObject.TraitName
|
||||
|
||||
traitIsDiscreteOrNumeric := traitObject.DiscreteOrNumeric
|
||||
if (traitIsDiscreteOrNumeric != "Numeric"){
|
||||
return false, false, 0, nil, nil, 0, 0, errors.New("GetOffspringNumericTraitInfo called with non-numeric trait.")
|
||||
}
|
||||
|
||||
modelExists, _ := geneticPredictionModels.GetGeneticPredictionModelBytes(traitName)
|
||||
if (modelExists == false){
|
||||
// Prediction is not possible for this trait
|
||||
return false, false, 0, nil, nil, 0, 0, nil
|
||||
}
|
||||
|
||||
traitLociList := traitObject.LociList
|
||||
|
||||
// First we count up the quantity of parental phased loci
|
||||
// We only count the quantity of phased loci for loci which are known for both parents
|
||||
|
||||
quantityOfParentalPhasedLoci := 0
|
||||
|
||||
for _, rsID := range traitLociList{
|
||||
|
||||
person1LocusValue, exists := person1LocusValuesMap[rsID]
|
||||
if (exists == false){
|
||||
continue
|
||||
}
|
||||
|
||||
person2LocusValue, exists := person2LocusValuesMap[rsID]
|
||||
if (exists == false){
|
||||
continue
|
||||
}
|
||||
|
||||
person1LocusIsPhased := person1LocusValue.LocusIsPhased
|
||||
if (person1LocusIsPhased == true){
|
||||
quantityOfParentalPhasedLoci += 1
|
||||
}
|
||||
|
||||
person2LocusIsPhased := person2LocusValue.LocusIsPhased
|
||||
if (person2LocusIsPhased == true){
|
||||
quantityOfParentalPhasedLoci += 1
|
||||
}
|
||||
}
|
||||
|
||||
// Next, we create 100 prospective offspring genomes.
|
||||
|
||||
anyLocusValueExists, prospectiveOffspringGenomesList, err := getProspectiveOffspringGenomesList(traitLociList, person1LocusValuesMap, person2LocusValuesMap)
|
||||
if (err != nil) { return false, false, 0, nil, nil, 0, 0, err }
|
||||
if (anyLocusValueExists == false){
|
||||
return true, false, 0, nil, nil, 0, 0, nil
|
||||
}
|
||||
|
||||
// A list of predicted outcomes for each offspring
|
||||
predictedOutcomesList := make([]float64, 0)
|
||||
|
||||
accuracyRangesMap := make(map[int]float64)
|
||||
quantityOfLociTested := 0
|
||||
|
||||
for index, offspringGenomeMap := range prospectiveOffspringGenomesList{
|
||||
|
||||
neuralNetworkExists, predictionIsKnown, predictedOutcome, predictionAccuracyRangesMap, currentQuantityOfLociTested, _, err := createPersonGeneticAnalysis.GetGenomeNumericTraitAnalysis(traitObject, offspringGenomeMap, false)
|
||||
if (err != nil){ return false, false, 0, nil, nil, 0, 0, err }
|
||||
if (neuralNetworkExists == false){
|
||||
return false, false, 0, nil, nil, 0, 0, errors.New("GetGenomeNumericTraitAnalysis claiming that neural network doesn't exist when we already checked.")
|
||||
}
|
||||
if (predictionIsKnown == false){
|
||||
return false, false, 0, nil, nil, 0, 0, errors.New("GetGenomeNumericTraitAnalysis claiming that prediction is impossible when we already know at least 1 locus value exists for trait.")
|
||||
}
|
||||
|
||||
predictedOutcomesList = append(predictedOutcomesList, predictedOutcome)
|
||||
|
||||
if (index == 0){
|
||||
// These values should be the same for each predicted offspring
|
||||
accuracyRangesMap = predictionAccuracyRangesMap
|
||||
quantityOfLociTested = currentQuantityOfLociTested
|
||||
}
|
||||
}
|
||||
|
||||
// We calculate the average predicted outcome
|
||||
|
||||
outcomesSum := float64(0)
|
||||
|
||||
for _, predictedOutcome := range predictedOutcomesList{
|
||||
outcomesSum += predictedOutcome
|
||||
}
|
||||
|
||||
averageOutcome := outcomesSum/100
|
||||
|
||||
return true, true, averageOutcome, accuracyRangesMap, predictedOutcomesList, quantityOfLociTested, quantityOfParentalPhasedLoci, nil
|
||||
}
|
||||
|
||||
// This function will return a list of 100 prospective offspring genomes
|
||||
// Each genome represents an equal-probability offspring genome from both people's genomes
|
||||
// This function takes into account the effects of genetic linkage
|
||||
|
|
|
@ -150,6 +150,14 @@ func CreatePersonGeneticAnalysis(genomesList []prepareRawGenomes.RawGenomeWithMe
|
|||
if (err != nil) { return false, "", err }
|
||||
|
||||
analysisDiscreteTraitsMap[traitName] = personTraitAnalysisObject
|
||||
} else {
|
||||
|
||||
//traitIsDiscreteOrNumeric == "Numeric"
|
||||
|
||||
personTraitAnalysisObject, err := GetPersonNumericTraitAnalysis(genomesWithMetadataList, traitObject)
|
||||
if (err != nil) { return false, "", err }
|
||||
|
||||
analysisNumericTraitsMap[traitName] = personTraitAnalysisObject
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -993,6 +1001,79 @@ func GetPersonDiscreteTraitAnalysis(inputGenomesWithMetadataList []prepareRawGen
|
|||
}
|
||||
|
||||
|
||||
//Outputs:
|
||||
// -geneticAnalysis.PersonNumericTraitInfo: Trait analysis object
|
||||
// -error
|
||||
func GetPersonNumericTraitAnalysis(inputGenomesWithMetadataList []prepareRawGenomes.GenomeWithMetadata, traitObject traits.Trait)(geneticAnalysis.PersonNumericTraitInfo, error){
|
||||
|
||||
// Map Structure: Genome Identifier -> PersonGenomeNumericTraitInfo
|
||||
newPersonTraitInfoMap := make(map[[16]byte]geneticAnalysis.PersonGenomeNumericTraitInfo)
|
||||
|
||||
for _, genomeWithMetadataObject := range inputGenomesWithMetadataList{
|
||||
|
||||
genomeIdentifier := genomeWithMetadataObject.GenomeIdentifier
|
||||
genomeMap := genomeWithMetadataObject.GenomeMap
|
||||
|
||||
neuralNetworkExists, neuralNetworkOutcomeIsKnown, predictedOutcome, predictionConfidenceRangesMap, quantityOfLociKnown, quantityOfPhasedLoci, err := GetGenomeNumericTraitAnalysis(traitObject, genomeMap, true)
|
||||
if (err != nil) { return geneticAnalysis.PersonNumericTraitInfo{}, err }
|
||||
if (neuralNetworkExists == false || neuralNetworkOutcomeIsKnown == false){
|
||||
continue
|
||||
}
|
||||
|
||||
newPersonGenomeTraitInfo := geneticAnalysis.PersonGenomeNumericTraitInfo{
|
||||
PredictedOutcome: predictedOutcome,
|
||||
ConfidenceRangesMap: predictionConfidenceRangesMap,
|
||||
QuantityOfLociKnown: quantityOfLociKnown,
|
||||
QuantityOfPhasedLoci: quantityOfPhasedLoci,
|
||||
}
|
||||
|
||||
newPersonTraitInfoMap[genomeIdentifier] = newPersonGenomeTraitInfo
|
||||
}
|
||||
|
||||
newPersonTraitInfoObject := geneticAnalysis.PersonNumericTraitInfo{
|
||||
TraitInfoMap: newPersonTraitInfoMap,
|
||||
}
|
||||
|
||||
if (len(newPersonTraitInfoMap) <= 1){
|
||||
// We do not need to check for conflicts, there is only <=1 genome with trait information
|
||||
// Nothing left to do. Analysis is complete.
|
||||
return newPersonTraitInfoObject, nil
|
||||
}
|
||||
|
||||
// We check for conflicts
|
||||
|
||||
getConflictExistsBool := func()(bool, error){
|
||||
|
||||
// We check to see if the analysis results are the same for all genomes
|
||||
|
||||
firstItemReached := false
|
||||
personGenomeTraitInfoObject := geneticAnalysis.PersonGenomeNumericTraitInfo{}
|
||||
|
||||
for _, genomeTraitInfoObject := range newPersonTraitInfoMap{
|
||||
|
||||
if (firstItemReached == false){
|
||||
personGenomeTraitInfoObject = genomeTraitInfoObject
|
||||
continue
|
||||
}
|
||||
|
||||
areEqual := reflect.DeepEqual(personGenomeTraitInfoObject, genomeTraitInfoObject)
|
||||
if (areEqual == false){
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
conflictExists, err := getConflictExistsBool()
|
||||
if (err != nil) { return geneticAnalysis.PersonNumericTraitInfo{}, err }
|
||||
|
||||
newPersonTraitInfoObject.ConflictExists = conflictExists
|
||||
|
||||
return newPersonTraitInfoObject, nil
|
||||
}
|
||||
|
||||
|
||||
//Outputs:
|
||||
// -int: Base pair disease locus risk weight
|
||||
// -bool: Base pair disease locus odds ratio known
|
||||
|
@ -1021,7 +1102,7 @@ func GetGenomePolygenicDiseaseLocusRiskInfo(locusRiskWeightsMap map[string]int,
|
|||
return riskWeight, true, oddsRatio, nil
|
||||
}
|
||||
|
||||
// We use this to generate trait predictions using a neural network
|
||||
// We use this to generate discrete trait predictions using a neural network
|
||||
// The alternative prediction method is to use Rules (see GetGenomeTraitAnalysis_Rules)
|
||||
//Outputs:
|
||||
// -bool: Trait Neural network analysis available (if false, we can't predict this trait using a neural network)
|
||||
|
@ -1263,6 +1344,65 @@ func GetGenomePassesDiscreteTraitRuleStatus(ruleLociList []traits.RuleLocus, gen
|
|||
}
|
||||
|
||||
|
||||
// We use this to generate numeric trait predictions using a neural network
|
||||
//Outputs:
|
||||
// -bool: Trait Neural network analysis available (if false, we can't predict this trait using a neural network)
|
||||
// -bool: Neural network outcome is known (at least 1 locus value is known which is needed for the neural network
|
||||
// -float64: The predicted value (Example: Height in centimeters)
|
||||
// -map[int]float64: Accuracy ranges map
|
||||
// -Map Structure: Probability prediction is accurate (X) -> Distance from predictoin that must be travelled in both directions to
|
||||
// create a range in which the true value will fall into, X% of the time
|
||||
// -int: Quantity of loci known
|
||||
// -int: Quantity of phased loci
|
||||
// -error
|
||||
func GetGenomeNumericTraitAnalysis(traitObject traits.Trait, genomeMap map[int64]locusValue.LocusValue, checkForAliases bool)(bool, bool, float64, map[int]float64, int, int, error){
|
||||
|
||||
getGenomeLocusValuesMap := func()(map[int64]locusValue.LocusValue, error){
|
||||
|
||||
if (checkForAliases == false){
|
||||
// We don't need to check for rsID aliases.
|
||||
return genomeMap, nil
|
||||
}
|
||||
|
||||
traitLociList := traitObject.LociList
|
||||
|
||||
// This map contains the locus values for the genome
|
||||
// If a locus's entry doesn't exist, its value is unknown
|
||||
// Map Structure: Locus rsID -> Locus Value
|
||||
genomeLocusValuesMap := make(map[int64]locusValue.LocusValue)
|
||||
|
||||
for _, locusRSID := range traitLociList{
|
||||
|
||||
locusBasePairKnown, _, _, _, locusValueObject, err := GetLocusValueFromGenomeMap(checkForAliases, genomeMap, locusRSID)
|
||||
if (err != nil) { return nil, err }
|
||||
if (locusBasePairKnown == false){
|
||||
continue
|
||||
}
|
||||
|
||||
genomeLocusValuesMap[locusRSID] = locusValueObject
|
||||
}
|
||||
|
||||
return genomeLocusValuesMap, nil
|
||||
}
|
||||
|
||||
genomeLocusValuesMap, err := getGenomeLocusValuesMap()
|
||||
if (err != nil) { return false, false, 0, nil, 0, 0, err }
|
||||
|
||||
traitName := traitObject.TraitName
|
||||
|
||||
neuralNetworkModelExists, traitPredictionIsPossible, predictedOutcome, predictionAccuracyRangesMap, quantityOfLociKnown, quantityOfPhasedLoci, err := geneticPrediction.GetNeuralNetworkNumericTraitPredictionFromGenomeMap(traitName, genomeLocusValuesMap)
|
||||
if (err != nil) { return false, false, 0, nil, 0, 0, err }
|
||||
if (neuralNetworkModelExists == false){
|
||||
return false, false, 0, nil, 0, 0, nil
|
||||
}
|
||||
if (traitPredictionIsPossible == false){
|
||||
return true, false, 0, nil, 0, 0, nil
|
||||
}
|
||||
|
||||
return true, true, predictedOutcome, predictionAccuracyRangesMap, quantityOfLociKnown, quantityOfPhasedLoci, nil
|
||||
}
|
||||
|
||||
|
||||
// This function will retrieve the base pair of the locus from the input genome map
|
||||
// We use this function because each rsID has aliases, so we must sometimes check those aliases to find locus values
|
||||
//
|
||||
|
|
|
@ -469,15 +469,15 @@ type OffspringGenomePairNumericTraitInfo struct{
|
|||
// predicted value's range to be accurate, X% of the time?
|
||||
// For example: 50% accuracy requires a +/-5 point range, 80% accuracy requires a +-15 point range
|
||||
// Map Structure: Accuracy probability (0-100) -> Amount to add to value in both +/- directions so prediction is that accurate
|
||||
AverageConfidenceRangesMap map[int]float64
|
||||
PredictionConfidenceRangesMap map[int]float64
|
||||
|
||||
QuantityOfLociKnown int
|
||||
|
||||
// This describes the quantity of loci from both parents that are phased
|
||||
// For example, if there are 10 loci for this trait, and one parent has 10 phased loci and the other has 5,
|
||||
// this variable will have a value of 15
|
||||
QuantityOfParentalPhasedLoci int
|
||||
|
||||
QuantityOfLociKnown int
|
||||
|
||||
// A list of 100 offspring outcomes for 100 prospective offspring from the genome pair
|
||||
// Example: A list of heights for 100 prospective offspring
|
||||
SampleOffspringOutcomesList []float64
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
package geneticPrediction
|
||||
|
||||
// I am a neophyte in the ways of neural networks.
|
||||
// I am a novice in the ways of neural networks.
|
||||
// Machine learning experts should chime in and offer improvements.
|
||||
// We have to make sure that model inference remains very fast
|
||||
// Sorting matches by offspring total polygenic disease score will require inference on dozens of models for each match
|
||||
|
@ -27,7 +27,6 @@ import "encoding/gob"
|
|||
import "slices"
|
||||
import "errors"
|
||||
|
||||
//import "log"
|
||||
|
||||
type NeuralNetwork struct{
|
||||
|
||||
|
@ -283,13 +282,9 @@ func DecodeBytesToDiscreteTraitPredictionAccuracyInfoMap(inputBytes []byte)(Disc
|
|||
return newDiscreteTraitPredictionAccuracyInfoMap, nil
|
||||
}
|
||||
|
||||
type NumericTraitPredictionAccuracyInfoMap map[NumericTraitOutcomeInfo]NumericTraitPredictionAccuracyRangesMap
|
||||
type NumericTraitPredictionAccuracyInfoMap map[NumericTraitPredictionInfo]NumericTraitPredictionAccuracyRangesMap
|
||||
|
||||
type NumericTraitOutcomeInfo struct{
|
||||
|
||||
// This is the outcome which was predicted
|
||||
// Example: 150 centimeters
|
||||
OutcomeValue float64
|
||||
type NumericTraitPredictionInfo struct{
|
||||
|
||||
// This is a value between 0-100 which describes the percentage of the loci which were tested for the input for the prediction
|
||||
PercentageOfLociTested int
|
||||
|
@ -377,55 +372,9 @@ func GetNeuralNetworkDiscreteTraitPredictionFromGenomeMap(traitName string, geno
|
|||
traitRSIDsListCopy := slices.Clone(traitRSIDsList)
|
||||
slices.Sort(traitRSIDsListCopy)
|
||||
|
||||
// In the inputLayer, each locus value is represented by 3 neurons:
|
||||
// 1. LocusExists/LocusIsPhased
|
||||
// -0 = Locus value is unknown
|
||||
// -0.5 = Locus Is known, phase is unknown
|
||||
// -1 = Locus Is Known, phase is known
|
||||
// 2. Allele1 Locus Value (Value between 0-1)
|
||||
// -0 = Value is unknown
|
||||
// 3. Allele2 Locus Value (Value between 0-1)
|
||||
// -0 = Value is unknown
|
||||
//
|
||||
neuralNetworkInput := make([]float32, 0)
|
||||
|
||||
quantityOfLociKnown := 0
|
||||
quantityOfPhasedLoci := 0
|
||||
|
||||
for _, rsID := range traitRSIDsListCopy{
|
||||
|
||||
userLocusValue, exists := genomeMap[rsID]
|
||||
if (exists == false){
|
||||
neuralNetworkInput = append(neuralNetworkInput, 0, 0, 0)
|
||||
continue
|
||||
}
|
||||
|
||||
quantityOfLociKnown += 1
|
||||
|
||||
locusAllele1 := userLocusValue.Base1Value
|
||||
locusAllele2 := userLocusValue.Base2Value
|
||||
locusIsPhased := userLocusValue.LocusIsPhased
|
||||
|
||||
getNeuron1 := func()float32{
|
||||
if (locusIsPhased == false){
|
||||
return 0.5
|
||||
}
|
||||
|
||||
quantityOfPhasedLoci += 1
|
||||
return 1
|
||||
}
|
||||
|
||||
neuron1 := getNeuron1()
|
||||
|
||||
neuron2, err := convertAlleleToNeuron(locusAllele1)
|
||||
neuralNetworkInput, quantityOfLociKnown, quantityOfPhasedLoci, err := createInputNeuralNetworkLayerFromGenomeMap(traitRSIDsListCopy, genomeMap)
|
||||
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){
|
||||
// We can't predict anything about this trait for this genome
|
||||
return true, false, "", 0, 0, 0, nil
|
||||
|
@ -522,6 +471,179 @@ func GetNeuralNetworkDiscreteTraitPredictionFromGenomeMap(traitName string, geno
|
|||
return true, true, predictedOutcomeName, predictionAccuracy, quantityOfLociKnown, quantityOfPhasedLoci, nil
|
||||
}
|
||||
|
||||
//Outputs:
|
||||
// -bool: Neural network model exists for this trait (trait prediction is possible for this trait)
|
||||
// -bool: Trait prediction is possible for this user (User has at least 1 known trait locus value)
|
||||
// -float64: Predicted trait outcome (Example: Height in centimeters)
|
||||
// -map[int]float64: Accuracy ranges map
|
||||
// -Map Structure: Probability prediction is accurate (X) -> Distance from prediction that must be travelled in both directions to
|
||||
// create a range in which the true value will fall into, X% of the time
|
||||
// -int: Quantity of loci known
|
||||
// -int: Quantity of phased loci
|
||||
// -error
|
||||
func GetNeuralNetworkNumericTraitPredictionFromGenomeMap(traitName string, genomeMap map[int64]locusValue.LocusValue)(bool, bool, float64, map[int]float64, int, int, error){
|
||||
|
||||
traitObject, err := traits.GetTraitObject(traitName)
|
||||
if (err != nil) { return false, false, 0, nil, 0, 0, err }
|
||||
|
||||
traitIsDiscreteOrNumeric := traitObject.DiscreteOrNumeric
|
||||
if (traitIsDiscreteOrNumeric != "Numeric"){
|
||||
return false, false, 0, nil, 0, 0, errors.New("GetNeuralNetworkNumericTraitPredictionFromGenomeMap called with non-discrete trait: " + traitName)
|
||||
}
|
||||
|
||||
// This is a map of rsIDs which influence this trait
|
||||
traitRSIDsList := traitObject.LociList
|
||||
|
||||
if (len(traitRSIDsList) == 0){
|
||||
// Prediction is not possible for this trait
|
||||
return false, false, 0, nil, 0, 0, nil
|
||||
}
|
||||
|
||||
predictionModelExists, predictionModelBytes := geneticPredictionModels.GetGeneticPredictionModelBytes(traitName)
|
||||
if (predictionModelExists == false){
|
||||
// Prediction is not possible for this trait
|
||||
return false, false, 0, nil, 0, 0, nil
|
||||
}
|
||||
|
||||
traitRSIDsListCopy := slices.Clone(traitRSIDsList)
|
||||
slices.Sort(traitRSIDsListCopy)
|
||||
|
||||
neuralNetworkInput, quantityOfLociKnown, quantityOfPhasedLoci, err := createInputNeuralNetworkLayerFromGenomeMap(traitRSIDsListCopy, genomeMap)
|
||||
if (err != nil) { return false, false, 0, nil, 0, 0, err }
|
||||
|
||||
if (quantityOfLociKnown == 0){
|
||||
// We can't predict anything about this trait for this genome
|
||||
return true, false, 0, nil, 0, 0, nil
|
||||
}
|
||||
|
||||
neuralNetworkObject, err := DecodeBytesToNeuralNetworkObject(predictionModelBytes)
|
||||
if (err != nil) { return false, false, 0, nil, 0, 0, err }
|
||||
|
||||
outputLayer, err := GetNeuralNetworkRawPrediction(&neuralNetworkObject, true, neuralNetworkInput)
|
||||
if (err != nil) { return false, false, 0, nil, 0, 0, err }
|
||||
|
||||
predictedOutcomeValue, err := GetNumericOutcomeValueFromOutputLayer(traitName, outputLayer)
|
||||
if (err != nil) { return false, false, 0, nil, 0, 0, err }
|
||||
|
||||
modelTraitAccuracyInfoFile, err := geneticPredictionModels.GetPredictionModelNumericTraitAccuracyInfoBytes(traitName)
|
||||
if (err != nil) { return false, false, 0, nil, 0, 0, err }
|
||||
|
||||
modelTraitAccuracyInfoMap, err := DecodeBytesToNumericTraitPredictionAccuracyInfoMap(modelTraitAccuracyInfoFile)
|
||||
if (err != nil) { return false, false, 0, nil, 0, 0, err }
|
||||
|
||||
// We create a prediction confidence ranges map for our prediction
|
||||
|
||||
getPredictionConfidenceRangesMap := func()map[int]float64{
|
||||
|
||||
totalNumberOfTraitLoci := len(traitRSIDsList)
|
||||
|
||||
proportionOfLociTested := float64(quantityOfLociKnown)/float64(totalNumberOfTraitLoci)
|
||||
percentageOfLociTested := int(proportionOfLociTested * 100)
|
||||
|
||||
proportionOfPhasedLoci := float64(quantityOfPhasedLoci)/float64(totalNumberOfTraitLoci)
|
||||
percentageOfPhasedLoci := int(proportionOfPhasedLoci * 100)
|
||||
|
||||
// This is a value between 0 and 100 that represents the most similar confidence ranges map for this prediction
|
||||
var closestPredictionConfidenceRangesMap map[int]float64
|
||||
|
||||
// This is a value that represents the distance our closest prediction confidence ranges map has from the current prediction
|
||||
// Consider each prediction accuracy value on an (X,Y) coordinate plane
|
||||
// X = Number of loci tested
|
||||
// Y = Number of phased loci
|
||||
closestPredictionConfidenceRangesMapDistance := float64(0)
|
||||
|
||||
for traitOutcomeInfo, traitPredictionConfidenceRangesMap := range modelTraitAccuracyInfoMap{
|
||||
|
||||
currentPercentageOfLociTested := traitOutcomeInfo.PercentageOfLociTested
|
||||
currentPercentageOfPhasedLoci := traitOutcomeInfo.PercentageOfPhasedLoci
|
||||
|
||||
// Distance Formula for 2 coordinates (x1, y1) and (x2, y2):
|
||||
// distance = √((x2 - x1)^2 + (y2 - y1)^2)
|
||||
|
||||
differenceInX := float64(currentPercentageOfLociTested - percentageOfLociTested)
|
||||
differenceInY := float64(currentPercentageOfPhasedLoci - percentageOfPhasedLoci)
|
||||
|
||||
distance := math.Sqrt(math.Pow(differenceInX, 2) + math.Pow(differenceInY, 2))
|
||||
|
||||
if (distance == 0){
|
||||
// We found the exact prediction confidence ranges map
|
||||
return traitPredictionConfidenceRangesMap
|
||||
}
|
||||
|
||||
if (closestPredictionConfidenceRangesMap == nil || distance < closestPredictionConfidenceRangesMapDistance){
|
||||
closestPredictionConfidenceRangesMapDistance = distance
|
||||
closestPredictionConfidenceRangesMap = traitPredictionConfidenceRangesMap
|
||||
}
|
||||
}
|
||||
|
||||
return closestPredictionConfidenceRangesMap
|
||||
}
|
||||
|
||||
predictionConfidenceRangesMap := getPredictionConfidenceRangesMap()
|
||||
|
||||
return true, true, predictedOutcomeValue, predictionConfidenceRangesMap, quantityOfLociKnown, quantityOfPhasedLoci, nil
|
||||
}
|
||||
|
||||
|
||||
//Outputs:
|
||||
// -[]float32: Input layer for neural network
|
||||
// -int: Quantity of known loci
|
||||
// -int: Quantity of phased loci
|
||||
// -error
|
||||
func createInputNeuralNetworkLayerFromGenomeMap(rsidsList []int64, genomeMap map[int64]locusValue.LocusValue)([]float32, int, int, error){
|
||||
|
||||
// In the inputLayer, each locus value is represented by 3 neurons:
|
||||
// 1. LocusExists/LocusIsPhased
|
||||
// -0 = Locus value is unknown
|
||||
// -0.5 = Locus Is known, phase is unknown
|
||||
// -1 = Locus Is Known, phase is known
|
||||
// 2. Allele1 Locus Value (Value between 0-1)
|
||||
// -0 = Value is unknown
|
||||
// 3. Allele2 Locus Value (Value between 0-1)
|
||||
// -0 = Value is unknown
|
||||
//
|
||||
neuralNetworkInput := make([]float32, 0)
|
||||
|
||||
quantityOfLociKnown := 0
|
||||
quantityOfPhasedLoci := 0
|
||||
|
||||
for _, rsID := range rsidsList{
|
||||
|
||||
userLocusValue, exists := genomeMap[rsID]
|
||||
if (exists == false){
|
||||
neuralNetworkInput = append(neuralNetworkInput, 0, 0, 0)
|
||||
continue
|
||||
}
|
||||
|
||||
quantityOfLociKnown += 1
|
||||
|
||||
locusAllele1 := userLocusValue.Base1Value
|
||||
locusAllele2 := userLocusValue.Base2Value
|
||||
locusIsPhased := userLocusValue.LocusIsPhased
|
||||
|
||||
getNeuron1 := func()float32{
|
||||
if (locusIsPhased == false){
|
||||
return 0.5
|
||||
}
|
||||
|
||||
quantityOfPhasedLoci += 1
|
||||
return 1
|
||||
}
|
||||
|
||||
neuron1 := getNeuron1()
|
||||
|
||||
neuron2, err := convertAlleleToNeuron(locusAllele1)
|
||||
if (err != nil) { return nil, 0, 0, err }
|
||||
|
||||
neuron3, err := convertAlleleToNeuron(locusAllele2)
|
||||
if (err != nil) { return nil, 0, 0, err }
|
||||
|
||||
neuralNetworkInput = append(neuralNetworkInput, neuron1, neuron2, neuron3)
|
||||
}
|
||||
|
||||
return neuralNetworkInput, quantityOfLociKnown, quantityOfLociKnown, nil
|
||||
}
|
||||
|
||||
//Outputs:
|
||||
// -int: Number of loci values that are known
|
||||
// -int: Number of loci values that are known and phased
|
||||
|
@ -732,7 +854,7 @@ func getNeuralNetworkLayerSizes(traitName string)(int, int, int, int, error){
|
|||
case "Height":{
|
||||
// There are 3000 input neurons
|
||||
// There is 1 output neuron, representing a height value
|
||||
return 3000, 2, 2, 1, nil
|
||||
return 3000, 3, 2, 1, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1289,8 +1411,6 @@ func TrainNeuralNetwork(traitName string, traitIsNumeric bool, neuralNetworkObje
|
|||
err = gorgonia.Let(trainingDataExpectedOutputNode, outputTensor)
|
||||
if (err != nil) { return false, err }
|
||||
|
||||
// for i:=0; i < 10; i++{
|
||||
|
||||
err = virtualMachine.RunAll()
|
||||
if (err != nil) { return false, err }
|
||||
|
||||
|
@ -1301,9 +1421,6 @@ func TrainNeuralNetwork(traitName string, traitIsNumeric bool, neuralNetworkObje
|
|||
if (err != nil) { return false, err }
|
||||
|
||||
virtualMachine.Reset()
|
||||
// }
|
||||
|
||||
// log.Println(cost.Value())
|
||||
}
|
||||
|
||||
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
|
||||
//TODO: Perform sanity checks on data
|
||||
func VerifyPersonGeneticAnalysis(personAnalysisObject geneticAnalysis.PersonAnalysis)error{
|
||||
|
@ -995,6 +1067,13 @@ func VerifyPersonGeneticAnalysis(personAnalysisObject geneticAnalysis.PersonAnal
|
|||
if (err != nil) { return err }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
for _, genomeIdentifier := range allGenomeIdentifiersList{
|
||||
|
||||
_, _, _, _, _, _, err := GetPersonNumericTraitInfoFromGeneticAnalysis(personAnalysisObject, traitName, genomeIdentifier)
|
||||
if (err != nil) { return err }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1111,6 +1190,14 @@ func VerifyCoupleGeneticAnalysis(coupleAnalysisObject geneticAnalysis.CoupleAnal
|
|||
if (err != nil) { return err }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
for _, genomePairIdentifier := range allGenomePairIdentifiersList{
|
||||
|
||||
_, _, _, _, _, _, _, err := GetOffspringNumericTraitInfoFromGeneticAnalysis(coupleAnalysisObject, traitName, genomePairIdentifier)
|
||||
if (err != nil) { return err }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
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
|
||||
}
|
||||
|
||||
|
||||
// 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"
|
||||
|
||||
|
||||
//go:embed predictionModels/EyeColorModel.gob
|
||||
var predictionModel_EyeColor []byte
|
||||
|
||||
//go:embed predictionModels/LactoseToleranceModel.gob
|
||||
var predictionModel_LactoseTolerance []byte
|
||||
|
||||
//go:embed predictionModels/HeightModel.gob
|
||||
var predictionModel_Height []byte
|
||||
|
||||
|
||||
//Outputs:
|
||||
// -bool: Model exists
|
||||
// -[]byte
|
||||
|
@ -24,16 +35,20 @@ func GetGeneticPredictionModelBytes(traitName string)(bool, []byte){
|
|||
case "Lactose Tolerance":{
|
||||
return true, predictionModel_LactoseTolerance
|
||||
}
|
||||
case "Height":{
|
||||
return true, predictionModel_Height
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
//go:embed predictionModels/EyeColorModel.gob
|
||||
var predictionModel_EyeColor []byte
|
||||
//go:embed predictionModelAccuracies/EyeColorModelAccuracy.gob
|
||||
var predictionAccuracy_EyeColor []byte
|
||||
|
||||
//go:embed predictionModelAccuracies/LactoseToleranceModelAccuracy.gob
|
||||
var predictionAccuracy_LactoseTolerance []byte
|
||||
|
||||
//go:embed predictionModels/LactoseToleranceModel.gob
|
||||
var predictionModel_LactoseTolerance []byte
|
||||
|
||||
// The files returned by this function are .gob encoded geneticPrediction.DiscreteTraitPredictionAccuracyInfoMap objects
|
||||
func GetPredictionModelDiscreteTraitAccuracyInfoBytes(traitName string)([]byte, error){
|
||||
|
@ -47,12 +62,22 @@ func GetPredictionModelDiscreteTraitAccuracyInfoBytes(traitName string)([]byte,
|
|||
}
|
||||
}
|
||||
|
||||
return nil, errors.New("GetPredictionModelTraitAccuracyInfoFile called with unknown traitName: " + traitName)
|
||||
return nil, errors.New("GetPredictionModelDiscreteTraitAccuracyInfoBytes called with unknown traitName: " + traitName)
|
||||
}
|
||||
|
||||
//go:embed predictionModelAccuracies/EyeColorModelAccuracy.gob
|
||||
var predictionAccuracy_EyeColor []byte
|
||||
//go:embed predictionModelAccuracies/HeightModelAccuracy.gob
|
||||
var predictionAccuracy_Height []byte
|
||||
|
||||
// The files returned by this function are .gob encoded geneticPrediction.NumericTraitPredictionAccuracyInfoMap objects
|
||||
func GetPredictionModelNumericTraitAccuracyInfoBytes(traitName string)([]byte, error){
|
||||
|
||||
switch traitName{
|
||||
case "Height":{
|
||||
return predictionAccuracy_Height, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New("GetPredictionModelNumericTraitAccuracyInfoBytes called with unknown traitName: " + traitName)
|
||||
}
|
||||
|
||||
//go:embed predictionModelAccuracies/LactoseToleranceModelAccuracy.gob
|
||||
var predictionAccuracy_LactoseTolerance []byte
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import "seekia/internal/genetics/geneticPrediction"
|
|||
|
||||
func TestGeneticPredictionModels(t *testing.T){
|
||||
|
||||
traitNamesList := []string{"Eye Color", "Lactose Tolerance"}
|
||||
traitNamesList := []string{"Eye Color", "Lactose Tolerance", "Height"}
|
||||
|
||||
for _, traitName := range traitNamesList{
|
||||
|
||||
|
@ -42,6 +42,21 @@ func TestGeneticPredictionModelAccuracies(t *testing.T){
|
|||
t.Fatalf("DecodeBytesToDiscreteTraitPredictionAccuracyInfoMap failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
numericTraitNamesList := []string{"Height"}
|
||||
|
||||
for _, traitName := range numericTraitNamesList{
|
||||
|
||||
accuracyInfoBytes, err := geneticPredictionModels.GetPredictionModelNumericTraitAccuracyInfoBytes(traitName)
|
||||
if (err != nil){
|
||||
t.Fatalf("GetPredictionModelNumericTraitAccuracyInfoBytes failed: " + err.Error())
|
||||
}
|
||||
|
||||
_, err = geneticPrediction.DecodeBytesToNumericTraitPredictionAccuracyInfoMap(accuracyInfoBytes)
|
||||
if (err != nil){
|
||||
t.Fatalf("DecodeBytesToNumericTraitPredictionAccuracyInfoMap failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
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)
|
||||
if (err != nil) { return false, err }
|
||||
|
||||
numberOfTrainingDatas := len(trainingSetFilepathsList)
|
||||
numberOfTrainingDatasString := helpers.ConvertIntToString(numberOfTrainingDatas)
|
||||
// The number of rounds of training for the training data set
|
||||
totalQuantityOfRoundsToRun := 2
|
||||
|
||||
quantityOfTrainingDatasInSet := len(trainingSetFilepathsList)
|
||||
|
||||
quantityOfTrainingDatas := len(trainingSetFilepathsList) * totalQuantityOfRoundsToRun
|
||||
quantityOfTrainingDatasString := helpers.ConvertIntToString(quantityOfTrainingDatas)
|
||||
|
||||
// This keeps track of how many training rounds we have completed
|
||||
// With each round, we shuffle the training data list and train the model again
|
||||
trainingRoundsCompleted := 0
|
||||
|
||||
// This keeps track of how far along we are in training
|
||||
trainingDataIndex := 0
|
||||
|
||||
// This keeps track of how many examples we have trained during all rounds
|
||||
quantityOfExamplesTrained := 0
|
||||
|
||||
// Outputs:
|
||||
// -bool: User stopped training
|
||||
// -bool: Another training data exists
|
||||
|
@ -1190,11 +1202,26 @@ func setStartAndMonitorTrainModelPage(window fyne.Window, traitName string, prev
|
|||
return true, false, geneticPrediction.TrainingData{}, nil
|
||||
}
|
||||
|
||||
if (trainingDataIndex == numberOfTrainingDatas){
|
||||
// We are done training.
|
||||
if (trainingDataIndex == quantityOfTrainingDatasInSet){
|
||||
// We are done training this set
|
||||
|
||||
trainingRoundsCompleted += 1
|
||||
|
||||
if (trainingRoundsCompleted == totalQuantityOfRoundsToRun){
|
||||
// We are done training
|
||||
return false, false, geneticPrediction.TrainingData{}, nil
|
||||
}
|
||||
|
||||
// We train another round
|
||||
trainingDataIndex = 0
|
||||
|
||||
// We deterministically randomize the order of the training data for the next round
|
||||
|
||||
pseudorandomNumberGenerator.Shuffle(len(trainingSetFilepathsList), func(i int, j int){
|
||||
trainingSetFilepathsList[i], trainingSetFilepathsList[j] = trainingSetFilepathsList[j], trainingSetFilepathsList[i]
|
||||
})
|
||||
}
|
||||
|
||||
trainingDataFilepath := trainingSetFilepathsList[trainingDataIndex]
|
||||
|
||||
fileExists, fileContents, err := localFilesystem.GetFileContents(trainingDataFilepath)
|
||||
|
@ -1206,15 +1233,15 @@ func setStartAndMonitorTrainModelPage(window fyne.Window, traitName string, prev
|
|||
trainingDataObject, err := geneticPrediction.DecodeBytesToTrainingDataObject(fileContents)
|
||||
if (err != nil) { return false, false, geneticPrediction.TrainingData{}, err }
|
||||
|
||||
|
||||
trainingDataIndex += 1
|
||||
quantityOfExamplesTrained += 1
|
||||
|
||||
numberOfExamplesTrainedString := helpers.ConvertIntToString(trainingDataIndex + 1)
|
||||
numberOfExamplesProgress := "Trained " + numberOfExamplesTrainedString + "/" + numberOfTrainingDatasString + " Examples"
|
||||
quantityOfExamplesTrainedString := helpers.ConvertIntToString(quantityOfExamplesTrained)
|
||||
numberOfExamplesProgress := "Trained " + quantityOfExamplesTrainedString + "/" + quantityOfTrainingDatasString + " Examples"
|
||||
|
||||
progressDetailsBinding.Set(numberOfExamplesProgress)
|
||||
|
||||
newProgressFloat64 := float64(trainingDataIndex)/float64(numberOfTrainingDatas)
|
||||
newProgressFloat64 := float64(quantityOfExamplesTrained)/float64(quantityOfTrainingDatas)
|
||||
|
||||
err = progressPercentageBinding.Set(newProgressFloat64)
|
||||
if (err != nil) { return false, false, geneticPrediction.TrainingData{}, err }
|
||||
|
@ -1661,9 +1688,8 @@ func setStartAndMonitorTestModelPage(window fyne.Window, traitName string, previ
|
|||
|
||||
// We use this map to count up the information about predictions
|
||||
// We use information from this map to construct the final accuracy information map
|
||||
// Map Structure: NumericTraitOutcomeInfo -> List of true outcomes
|
||||
traitPredictionInfoMap := make(map[geneticPrediction.NumericTraitOutcomeInfo][]float64)
|
||||
|
||||
// Map Structure: NumericTraitPredictionInfo -> []float64 (List of distances for each prediction)
|
||||
traitPredictionInfoMap := make(map[geneticPrediction.NumericTraitPredictionInfo][]float64)
|
||||
|
||||
_, testingSetFilepathsList, err := getTrainingAndTestingDataFilepathLists(traitName)
|
||||
if (err != nil) { return false, nil, err }
|
||||
|
@ -1712,17 +1738,17 @@ func setStartAndMonitorTestModelPage(window fyne.Window, traitName string, previ
|
|||
trainingDataInputLayer := trainingDataObject.InputLayer
|
||||
trainingDataExpectedOutputLayer := trainingDataObject.OutputLayer
|
||||
|
||||
predictionLayer, err := geneticPrediction.GetNeuralNetworkRawPrediction(&neuralNetworkObject, false, trainingDataInputLayer)
|
||||
if (len(trainingDataExpectedOutputLayer) != 1){
|
||||
return false, nil, errors.New("Neural network training data prediction output layer length is not 1.")
|
||||
}
|
||||
|
||||
predictionLayer, err := geneticPrediction.GetNeuralNetworkRawPrediction(&neuralNetworkObject, true, trainingDataInputLayer)
|
||||
if (err != nil) { return false, nil, err }
|
||||
|
||||
if (len(predictionLayer) != 1){
|
||||
return false, nil, errors.New("Neural network numeric prediction output layer length is not 1.")
|
||||
}
|
||||
|
||||
if (len(trainingDataExpectedOutputLayer) != 1){
|
||||
return false, nil, errors.New("Neural network training data prediction output layer length is not 1.")
|
||||
}
|
||||
|
||||
correctOutcomeValue, err := geneticPrediction.GetNumericOutcomeValueFromOutputLayer(traitName, trainingDataExpectedOutputLayer)
|
||||
if (err != nil) { return false, nil, err }
|
||||
|
||||
|
@ -1738,18 +1764,19 @@ func setStartAndMonitorTestModelPage(window fyne.Window, traitName string, previ
|
|||
proportionOfPhasedLoci := float64(numberOfKnownAndPhasedLoci)/float64(numberOfKnownLoci)
|
||||
percentageOfPhasedLoci := int(100*proportionOfPhasedLoci)
|
||||
|
||||
newNumericTraitOutcomeInfo := geneticPrediction.NumericTraitOutcomeInfo{
|
||||
OutcomeValue: predictedOutcomeValue,
|
||||
newNumericTraitPredictionInfo := geneticPrediction.NumericTraitPredictionInfo{
|
||||
PercentageOfLociTested: percentageOfLociTested,
|
||||
PercentageOfPhasedLoci: percentageOfPhasedLoci,
|
||||
}
|
||||
|
||||
existingList, exists := traitPredictionInfoMap[newNumericTraitOutcomeInfo]
|
||||
distanceFromCorrectValue := math.Abs(predictedOutcomeValue - correctOutcomeValue)
|
||||
|
||||
existingList, exists := traitPredictionInfoMap[newNumericTraitPredictionInfo]
|
||||
if (exists == false){
|
||||
traitPredictionInfoMap[newNumericTraitOutcomeInfo] = []float64{correctOutcomeValue}
|
||||
traitPredictionInfoMap[newNumericTraitPredictionInfo] = []float64{distanceFromCorrectValue}
|
||||
} else {
|
||||
existingList = append(existingList, correctOutcomeValue)
|
||||
traitPredictionInfoMap[newNumericTraitOutcomeInfo] = existingList
|
||||
existingList = append(existingList, distanceFromCorrectValue)
|
||||
traitPredictionInfoMap[newNumericTraitPredictionInfo] = existingList
|
||||
}
|
||||
|
||||
exampleIndexString := helpers.ConvertIntToString(index+1)
|
||||
|
@ -1764,72 +1791,51 @@ func setStartAndMonitorTestModelPage(window fyne.Window, traitName string, previ
|
|||
|
||||
// Now we construct the TraitAccuracyInfoMap
|
||||
|
||||
// This map stores the accuracy for each outcome
|
||||
traitPredictionAccuracyInfoMap := make(map[geneticPrediction.NumericTraitOutcomeInfo]geneticPrediction.NumericTraitPredictionAccuracyRangesMap)
|
||||
// This map stores the accuracy for each QuantityOfKnownLoci/QuantityOfPhasedLoci
|
||||
traitPredictionAccuracyInfoMap := make(map[geneticPrediction.NumericTraitPredictionInfo]geneticPrediction.NumericTraitPredictionAccuracyRangesMap)
|
||||
|
||||
for traitPredictionInfo, realOutcomesList := range traitPredictionInfoMap{
|
||||
for traitPredictionInfo, predictionDistancesList := range traitPredictionInfoMap{
|
||||
|
||||
if (len(realOutcomesList) == 0){
|
||||
return false, nil, errors.New("traitPredictionInfoMap contains empty realOutcomesList.")
|
||||
if (len(predictionDistancesList) == 0){
|
||||
return false, nil, errors.New("traitPredictionInfoMap contains empty predictionDistancesList.")
|
||||
}
|
||||
|
||||
// This is the predicted height value for this set of real outcomes
|
||||
predictionValue := traitPredictionInfo.OutcomeValue
|
||||
|
||||
// Map Structure: Accuracy Percentage (AP) -> Amount needed to deviate from prediction
|
||||
// for the value to be accurate (AP)% of the time
|
||||
newNumericTraitPredictionAccuracyRangesMap := make(map[int]float64)
|
||||
|
||||
rangeDistance := float64(0)
|
||||
|
||||
for {
|
||||
|
||||
rangeMin := predictionValue - rangeDistance
|
||||
rangeMax := predictionValue + rangeDistance
|
||||
|
||||
valuesInRangeList := make([]float64, 0)
|
||||
valuesOutOfRangeList := make([]float64, 0)
|
||||
|
||||
for _, outcomeValue := range realOutcomesList{
|
||||
|
||||
if (outcomeValue <= rangeMax && outcomeValue >= rangeMin){
|
||||
valuesInRangeList = append(valuesInRangeList, outcomeValue)
|
||||
} else {
|
||||
valuesOutOfRangeList = append(valuesOutOfRangeList, outcomeValue)
|
||||
}
|
||||
if (len(predictionDistancesList) < 5){
|
||||
// We don't have enough data to create an accuracyRanges map.
|
||||
continue
|
||||
}
|
||||
|
||||
quantityOfValuesInRange := len(valuesInRangeList)
|
||||
totalQuantityOfValues := len(realOutcomesList)
|
||||
// We sort the prediction distances list in ascending order
|
||||
slices.Sort(predictionDistancesList)
|
||||
|
||||
proportionOfValuesInRange := float64(quantityOfValuesInRange)/float64(totalQuantityOfValues)
|
||||
percentageOfValuesInRange := proportionOfValuesInRange * 100
|
||||
finalIndex := len(predictionDistancesList) - 1
|
||||
|
||||
if (percentageOfValuesInRange >= 1){
|
||||
percentageOfValuesInRangeInt := int(percentageOfValuesInRange)
|
||||
for index, distance := range predictionDistancesList{
|
||||
|
||||
newNumericTraitPredictionAccuracyRangesMap[percentageOfValuesInRangeInt] = rangeDistance
|
||||
}
|
||||
if (quantityOfValuesInRange == totalQuantityOfValues){
|
||||
newNumericTraitPredictionAccuracyRangesMap[100] = rangeDistance
|
||||
break
|
||||
proportionOfPredictionsWithinDistance := float64(index)/float64(finalIndex)
|
||||
|
||||
percentageOfPredictionsWithinDistance := int(100 * proportionOfPredictionsWithinDistance)
|
||||
|
||||
if (percentageOfPredictionsWithinDistance == 0){
|
||||
// 0% accuracy is not a useful metric for users
|
||||
continue
|
||||
}
|
||||
|
||||
// Now we increase rangeDistance
|
||||
// We find the distance to the next closest item in the list that isn't already in our range
|
||||
|
||||
nearestValueDistance := float64(0)
|
||||
|
||||
for index, outcomeValue := range valuesOutOfRangeList{
|
||||
|
||||
distance := math.Abs(predictionValue - outcomeValue)
|
||||
|
||||
if (index == 0 || distance < nearestValueDistance){
|
||||
nearestValueDistance = distance
|
||||
}
|
||||
_, exists := newNumericTraitPredictionAccuracyRangesMap[percentageOfPredictionsWithinDistance]
|
||||
if (exists == true){
|
||||
// There exists a value for this percentage already
|
||||
// This happens because we convert a float64 to an int
|
||||
// The existing percentage must be smaller than our current percentage
|
||||
// We want to keep that smaller percentage
|
||||
// For example, we would rather keep the 15.1% value than the 15.8% value.
|
||||
continue
|
||||
}
|
||||
|
||||
rangeDistance += nearestValueDistance
|
||||
newNumericTraitPredictionAccuracyRangesMap[percentageOfPredictionsWithinDistance] = distance
|
||||
}
|
||||
|
||||
traitPredictionAccuracyInfoMap[traitPredictionInfo] = newNumericTraitPredictionAccuracyRangesMap
|
||||
|
@ -1891,8 +1897,8 @@ func setViewModelTestingDiscreteTraitResultsPage(window fyne.Window, traitName s
|
|||
|
||||
getResultsGrid := func()(*fyne.Container, error){
|
||||
|
||||
outcomeNameTitle := getItalicLabelCentered("Outcome Name")
|
||||
emptyLabel1 := widget.NewLabel("")
|
||||
outcomeNameTitle := getItalicLabelCentered("Outcome Name")
|
||||
|
||||
predictionAccuracyTitle1 := getItalicLabelCentered("Prediction Accuracy")
|
||||
knownLociLabel_0to33 := getItalicLabelCentered("0-33% Known Loci")
|
||||
|
@ -1903,7 +1909,7 @@ func setViewModelTestingDiscreteTraitResultsPage(window fyne.Window, traitName s
|
|||
predictionAccuracyTitle3 := getItalicLabelCentered("Prediction Accuracy")
|
||||
knownLociLabel_67to100 := getItalicLabelCentered("67-100% Known Loci")
|
||||
|
||||
outcomeNameColumn := container.NewVBox(outcomeNameTitle, emptyLabel1, widget.NewSeparator())
|
||||
outcomeNameColumn := container.NewVBox(emptyLabel1, outcomeNameTitle, widget.NewSeparator())
|
||||
predictionAccuracyColumn_0to33 := container.NewVBox(predictionAccuracyTitle1, knownLociLabel_0to33, widget.NewSeparator())
|
||||
predictionAccuracyColumn_34to66 := container.NewVBox(predictionAccuracyTitle2, knownLociLabel_34to66, widget.NewSeparator())
|
||||
predictionAccuracyColumn_67to100 := container.NewVBox(predictionAccuracyTitle3, knownLociLabel_67to100, widget.NewSeparator())
|
||||
|
|
Loading…
Reference in a new issue