Added the Height trait to the Create Genetic Models utility.

This commit is contained in:
Simon Sarasova 2024-08-07 07:45:31 +00:00
parent d538afc7a2
commit fe754cb6a2
No known key found for this signature in database
GPG key ID: EEDA4103C9C36944
21 changed files with 861 additions and 199 deletions

View file

@ -6,6 +6,7 @@ Small and insignificant changes may not be included in this log.
## Unversioned Changes
* Added the Height trait to the Create Genetic Models utility. - *Simon Sarasova*
* Added LocusIsPhased information to the local user profile creation process. - *Simon Sarasova*
* Added the Height trait the traits package. Migrated locus metadata from json encoding to gob encoding. - *Simon Sarasova*
* Upgraded Fyne to version 2.5.0. - *Simon Sarasova*

View file

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

View file

@ -26,19 +26,19 @@ func ApplyCartoonEffect(inputImage image.Image, effectStrength int)(image.Image,
}
blurKernelSize, err := helpers.ScaleNumberProportionally(true, effectStrength, 0, 100, 1, 3)
blurKernelSize, err := helpers.ScaleIntProportionally(true, effectStrength, 0, 100, 1, 3)
if (err != nil) { return nil, err }
if (blurKernelSize % 2 == 0){
blurKernelSize += 1
}
edgeThreshold, err := helpers.ScaleNumberProportionally(false, effectStrength, 0, 100, 5, 200)
edgeThreshold, err := helpers.ScaleIntProportionally(false, effectStrength, 0, 100, 5, 200)
if (err != nil) { return nil, err }
oilFilterSize, err := helpers.ScaleNumberProportionally(true, effectStrength, 0, 100, 5, 20)
oilFilterSize, err := helpers.ScaleIntProportionally(true, effectStrength, 0, 100, 5, 20)
if (err != nil) { return nil, err }
oilLevels, err := helpers.ScaleNumberProportionally(true, effectStrength, 0, 100, 1, 3)
oilLevels, err := helpers.ScaleIntProportionally(true, effectStrength, 0, 100, 1, 3)
if (err != nil) { return nil, err }
options := CTOpts{
@ -78,7 +78,7 @@ func ApplyPencilEffect(inputImage image.Image, effectStrength int)(image.Image,
goeffectsImageObject, err := convertGolangImageObjectToGoeffectsImageObject(inputImage)
if (err != nil) { return nil, err }
blurAmount, err := helpers.ScaleNumberProportionally(true, effectStrength, 0, 100, 1, 20)
blurAmount, err := helpers.ScaleIntProportionally(true, effectStrength, 0, 100, 1, 20)
if (err != nil) { return nil, err }
if (blurAmount % 2 == 0) {
@ -119,7 +119,7 @@ func ApplyWireframeEffect(inputImage image.Image, effectStrength int, lightMode
grayscaleGoeffectsImage, err := grayscaleEffectObject.Apply(&goeffectsImageObject, 5)
if (err != nil) { return nil, err }
threshold, err := helpers.ScaleNumberProportionally(false, effectStrength, 0, 100, 10, 100)
threshold, err := helpers.ScaleIntProportionally(false, effectStrength, 0, 100, 10, 100)
if (err != nil) { return nil, err }
sobelEffectObject := NewSobel(threshold, lightMode)
@ -146,10 +146,10 @@ func ApplyOilPaintingEffect(inputImage image.Image, effectStrength int)(image.Im
return inputImage, nil
}
filterSize, err := helpers.ScaleNumberProportionally(true, effectStrength, 0, 100, 10, 30)
filterSize, err := helpers.ScaleIntProportionally(true, effectStrength, 0, 100, 10, 30)
if (err != nil) { return nil, err }
levels, err := helpers.ScaleNumberProportionally(true, effectStrength, 0, 100, 10, 70)
levels, err := helpers.ScaleIntProportionally(true, effectStrength, 0, 100, 10, 70)
if (err != nil) { return nil, err }
oilPaintingEffectObject := NewOilPainting(filterSize, levels)

View file

@ -40,7 +40,7 @@ func CreateCoupleGeneticAnalysis(person1GenomesList []prepareRawGenomes.RawGenom
person1PrepareRawGenomesUpdatePercentageCompleteFunction := func(newPercentage int)error{
newPercentageCompletion, err := helpers.ScaleNumberProportionally(true, newPercentage, 0, 100, 0, 25)
newPercentageCompletion, err := helpers.ScaleIntProportionally(true, newPercentage, 0, 100, 0, 25)
if (err != nil){ return err }
err = updatePercentageCompleteFunction(newPercentageCompletion)
@ -49,8 +49,18 @@ func CreateCoupleGeneticAnalysis(person1GenomesList []prepareRawGenomes.RawGenom
return nil
}
person1GenomesWithMetadataList, allPerson1RawGenomeIdentifiersList, person1HasMultipleGenomes, person1OnlyExcludeConflictsGenomeIdentifier, person1OnlyIncludeSharedGenomeIdentifier, err := prepareRawGenomes.GetGenomesWithMetadataListFromRawGenomesList(person1GenomesList, person1PrepareRawGenomesUpdatePercentageCompleteFunction)
anyUsefulLocationsExist, person1GenomesWithMetadataList, allPerson1RawGenomeIdentifiersList, person1HasMultipleGenomes, person1OnlyExcludeConflictsGenomeIdentifier, person1OnlyIncludeSharedGenomeIdentifier, err := prepareRawGenomes.GetGenomesWithMetadataListFromRawGenomesList(person1GenomesList, person1PrepareRawGenomesUpdatePercentageCompleteFunction)
if (err != nil) { return false, "", err }
if (anyUsefulLocationsExist == false){
// We should have checked for this when genomes were first imported.
return false, "", errors.New("CreateCoupleGeneticAnalysis called with person1GenomesList that does not contain any useful genomes")
}
if (len(person1GenomesList) > 1 && (len(person1GenomesList) != (len(person1GenomesWithMetadataList)-2)) ){
// If there is more than 1 genome, 2 combined genomes are created
// We are checking to make sure that none of the input genomes were dropped due to not having any locations
// We should have checked to make sure each input genome has useful locations when each genome was first imported.
return false, "", errors.New("CreateCoupleGeneticAnalysis called with person1GenomesList containing at least 1 genome without useful locations.")
}
processIsStopped := checkIfProcessIsStopped()
if (processIsStopped == true){
@ -59,7 +69,7 @@ func CreateCoupleGeneticAnalysis(person1GenomesList []prepareRawGenomes.RawGenom
person2PrepareRawGenomesUpdatePercentageCompleteFunction := func(newPercentage int)error{
newPercentageCompletion, err := helpers.ScaleNumberProportionally(true, newPercentage, 0, 100, 25, 50)
newPercentageCompletion, err := helpers.ScaleIntProportionally(true, newPercentage, 0, 100, 25, 50)
if (err != nil){ return err }
err = updatePercentageCompleteFunction(newPercentageCompletion)
@ -68,8 +78,18 @@ func CreateCoupleGeneticAnalysis(person1GenomesList []prepareRawGenomes.RawGenom
return nil
}
person2GenomesWithMetadataList, allPerson2RawGenomeIdentifiersList, person2HasMultipleGenomes, person2OnlyExcludeConflictsGenomeIdentifier, person2OnlyIncludeSharedGenomeIdentifier, err := prepareRawGenomes.GetGenomesWithMetadataListFromRawGenomesList(person2GenomesList, person2PrepareRawGenomesUpdatePercentageCompleteFunction)
anyUsefulLocationsExist, person2GenomesWithMetadataList, allPerson2RawGenomeIdentifiersList, person2HasMultipleGenomes, person2OnlyExcludeConflictsGenomeIdentifier, person2OnlyIncludeSharedGenomeIdentifier, err := prepareRawGenomes.GetGenomesWithMetadataListFromRawGenomesList(person2GenomesList, person2PrepareRawGenomesUpdatePercentageCompleteFunction)
if (err != nil) { return false, "", err }
if (anyUsefulLocationsExist == false){
// We should have checked for this when genomes were first imported.
return false, "", errors.New("CreateCoupleGeneticAnalysis called with person2GenomesList that does not contain any useful genomes")
}
if (len(person2GenomesList) > 1 && (len(person2GenomesList) != (len(person2GenomesWithMetadataList)-2)) ){
// If there is more than 1 genome, 2 combined genomes are created
// We are checking to make sure that none of the input genomes were dropped due to not having any locations
// We should have checked to make sure each input genome has useful locations when each genome was first imported.
return false, "", errors.New("CreateCoupleGeneticAnalysis called with person2GenomesList containing at least 1 genome without useful locations.")
}
processIsStopped = checkIfProcessIsStopped()
if (processIsStopped == true){
@ -936,7 +956,7 @@ func GetOffspringPolygenicDiseaseInfo_Fast(diseaseLociList []polygenicDiseases.D
offspringMaximumPossibleRiskWeightSum += locusMaximumWeight
}
offspringAverageDiseaseRiskScore, err := helpers.ScaleNumberProportionally(true, offspringSummedRiskWeights, offspringMinimumPossibleRiskWeightSum, offspringMaximumPossibleRiskWeightSum, 0, 10)
offspringAverageDiseaseRiskScore, err := helpers.ScaleIntProportionally(true, offspringSummedRiskWeights, offspringMinimumPossibleRiskWeightSum, offspringMaximumPossibleRiskWeightSum, 0, 10)
if (err != nil) { return false, 0, 0, err }
if (numberOfLociTested == 0){
@ -1061,7 +1081,7 @@ func GetOffspringPolygenicDiseaseInfo(diseaseLociList []polygenicDiseases.Diseas
offspringMaximumPossibleRiskWeightSum += locusMaximumWeight
}
offspringAverageDiseaseRiskScore, err := helpers.ScaleNumberProportionally(true, offspringSummedRiskWeights, offspringMinimumPossibleRiskWeightSum, offspringMaximumPossibleRiskWeightSum, 0, 10)
offspringAverageDiseaseRiskScore, err := helpers.ScaleIntProportionally(true, offspringSummedRiskWeights, offspringMinimumPossibleRiskWeightSum, offspringMaximumPossibleRiskWeightSum, 0, 10)
if (err != nil) { return false, 0, 0, nil, nil, err }
sampleOffspringRiskScoresList = append(sampleOffspringRiskScoresList, offspringAverageDiseaseRiskScore)

View file

@ -40,7 +40,7 @@ func CreatePersonGeneticAnalysis(genomesList []prepareRawGenomes.RawGenomeWithMe
prepareRawGenomesUpdatePercentageCompleteFunction := func(newPercentage int)error{
newPercentageCompletion, err := helpers.ScaleNumberProportionally(true, newPercentage, 0, 100, 0, 50)
newPercentageCompletion, err := helpers.ScaleIntProportionally(true, newPercentage, 0, 100, 0, 50)
if (err != nil){ return err }
err = updatePercentageCompleteFunction(newPercentageCompletion)
@ -49,8 +49,18 @@ func CreatePersonGeneticAnalysis(genomesList []prepareRawGenomes.RawGenomeWithMe
return nil
}
genomesWithMetadataList, allRawGenomeIdentifiersList, multipleGenomesExist, onlyExcludeConflictsGenomeIdentifier, onlyIncludeSharedGenomeIdentifier, err := prepareRawGenomes.GetGenomesWithMetadataListFromRawGenomesList(genomesList, prepareRawGenomesUpdatePercentageCompleteFunction)
anyUsefulLocationsExist, genomesWithMetadataList, allRawGenomeIdentifiersList, multipleGenomesExist, onlyExcludeConflictsGenomeIdentifier, onlyIncludeSharedGenomeIdentifier, err := prepareRawGenomes.GetGenomesWithMetadataListFromRawGenomesList(genomesList, prepareRawGenomesUpdatePercentageCompleteFunction)
if (err != nil) { return false, "", err }
if (anyUsefulLocationsExist == false){
// We should have checked for this when genomes were first imported.
return false, "", errors.New("CreatePersonGeneticAnalysis called with genomeList containing no genomes with useful locations.")
}
if (len(genomesList) > 1 && (len(genomesList) != (len(genomesWithMetadataList)-2)) ){
// If there is more than 1 genome, 2 combined genomes are created
// We are checking to make sure that none of the input genomes were dropped due to not having any locations
// We should have checked to make sure each input genome has useful locations when each genome was first imported.
return false, "", errors.New("CreatePersonGeneticAnalysis called with genomeList containing at least 1 genome without useful locations.")
}
// This map stores each genome's locus values
// Map Structure: Genome Identifier -> Genome locus values map (rsID -> Locus Value)
@ -716,7 +726,7 @@ func GetPersonGenomePolygenicDiseaseInfo(diseaseLociList []polygenicDiseases.Dis
return false, 0, 0, nil, nil
}
diseaseRiskScore, err := helpers.ScaleNumberProportionally(true, summedDiseaseRiskWeight, minimumPossibleRiskWeightSum, maximumPossibleRiskWeightSum, 0, 10)
diseaseRiskScore, err := helpers.ScaleIntProportionally(true, summedDiseaseRiskWeight, minimumPossibleRiskWeightSum, maximumPossibleRiskWeightSum, 0, 10)
if (err != nil) { return false, 0, 0, nil, err }
return true, diseaseRiskScore, numberOfLociTested, genomeLociInfoMap, nil

View file

@ -218,7 +218,7 @@ type DiscreteTraitPredictionAccuracyInfoMap map[DiscreteTraitOutcomeInfo]Discret
type DiscreteTraitOutcomeInfo struct{
// This is the outcome which was found
// This is the outcome which was predicted
// Example: "Blue"
OutcomeName string
@ -283,6 +283,65 @@ func DecodeBytesToDiscreteTraitPredictionAccuracyInfoMap(inputBytes []byte)(Disc
return newDiscreteTraitPredictionAccuracyInfoMap, nil
}
type NumericTraitPredictionAccuracyInfoMap map[NumericTraitOutcomeInfo]NumericTraitPredictionAccuracyRangesMap
type NumericTraitOutcomeInfo struct{
// This is the outcome which was predicted
// Example: 150 centimeters
OutcomeValue float64
// This is a value between 0-100 which describes the percentage of the loci which were tested for the input for the prediction
PercentageOfLociTested int
// This is a value between 0-100 which describes the percentage of the tested loci which were phased for the input for the prediction
PercentageOfPhasedLoci int
}
// Map Structure: Accuracy Percentage (AP) -> Amount needed to deviate from prediction for the value to be accurate (AP)% of the time
// For example, if the model predicted that someone was 150 centimeters tall, how many centimeters would we have to deviate in both directions
// in order for the true outcome to fall into the range 10% of the time, 20% of the time, 30% of the time, etc...
// Example:
// -90%+: 50 centimeters
// If you travel 50 centimeters in both directions from the prediction,
// the true height value will fall into this range 90% of the time.
// -50%+: 20 centimeters
// -10%+: 10 centimeters
type NumericTraitPredictionAccuracyRangesMap map[int]float64
func EncodeNumericTraitPredictionAccuracyInfoMapToBytes(inputMap NumericTraitPredictionAccuracyInfoMap)([]byte, error){
buffer := new(bytes.Buffer)
encoder := gob.NewEncoder(buffer)
err := encoder.Encode(inputMap)
if (err != nil) { return nil, err }
inputMapBytes := buffer.Bytes()
return inputMapBytes, nil
}
func DecodeBytesToNumericTraitPredictionAccuracyInfoMap(inputBytes []byte)(NumericTraitPredictionAccuracyInfoMap, error){
if (inputBytes == nil){
return nil, errors.New("DecodeBytesToNumericTraitPredictionAccuracyInfoMap called with nil inputBytes.")
}
buffer := bytes.NewBuffer(inputBytes)
decoder := gob.NewDecoder(buffer)
var newNumericTraitPredictionAccuracyInfoMap NumericTraitPredictionAccuracyInfoMap
err := decoder.Decode(&newNumericTraitPredictionAccuracyInfoMap)
if (err != nil){ return nil, err }
return newNumericTraitPredictionAccuracyInfoMap, 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)
@ -378,7 +437,7 @@ func GetNeuralNetworkDiscreteTraitPredictionFromGenomeMap(traitName string, geno
outputLayer, err := GetNeuralNetworkRawPrediction(&neuralNetworkObject, false, neuralNetworkInput)
if (err != nil) { return false, false, "", 0, 0, 0, err }
predictedOutcomeName, err := GetOutcomeNameFromOutputLayer(traitName, false, outputLayer)
predictedOutcomeName, err := GetDiscreteOutcomeNameFromOutputLayer(traitName, false, outputLayer)
if (err != nil) { return false, false, "", 0, 0, 0, err }
modelTraitAccuracyInfoFile, err := geneticPredictionModels.GetPredictionModelDiscreteTraitAccuracyInfoBytes(traitName)
@ -521,7 +580,7 @@ func GetLociInfoFromNetworkInputLayer(inputLayer []float32)(int, int, int, error
// Outputs:
// -string: Output Name (Example: "Blue")
// -error
func GetOutcomeNameFromOutputLayer(traitName string, verifyOutputLayer bool, outputLayer []float32)(string, error){
func GetDiscreteOutcomeNameFromOutputLayer(traitName string, verifyOutputLayer bool, outputLayer []float32)(string, error){
if (verifyOutputLayer == true){
@ -534,7 +593,7 @@ func GetOutcomeNameFromOutputLayer(traitName string, verifyOutputLayer bool, out
}
// We allow a small amount of inaccuracy due to the imprecise nature of floats.
if (summedNeurons > 1.1 || summedNeurons < .99){
if (summedNeurons > 1.01 || summedNeurons < .99){
summedNeuronsString := helpers.ConvertFloat32ToString(summedNeurons)
return "", errors.New("GetOutcomeNameFromOutputLayer called with layer containing neuron values which don't sum to 1: " + summedNeuronsString)
}
@ -607,6 +666,45 @@ func GetOutcomeNameFromOutputLayer(traitName string, verifyOutputLayer bool, out
}
// This function returns which outcome is being described from a neural network's final output layer
// This is only used for discrete traits
// Outputs:
// -float64: Output Value (example: 150 centimeters)
// -error
func GetNumericOutcomeValueFromOutputLayer(traitName string, outputLayer []float32)(float64, error){
if (len(outputLayer) != 1){
return 0, errors.New("GetNumericOutcomeValueFromOutputLayer called with output layer which is not length of 1")
}
outputNeuron := outputLayer[0]
if (outputNeuron < 0 || outputNeuron > 1){
return 0, errors.New("GetNumericOutcomeValueFromOutputLayer called with output layer contains out-of-bounds neuron")
}
getOutcomeMinAndMax := func()(float64, float64, error){
switch traitName{
case "Height":{
// Shortest person of all time: 54 cm
// Tallest person of all time: 272 cm
return 54, 272, nil
}
}
return 0, 0, errors.New("GetNumericOutcomeValueFromOutputLayer called with unknown traitName: " + traitName)
}
outcomeMin, outcomeMax, err := getOutcomeMinAndMax()
if (err != nil) { return 0, err }
outcomeValue, err := helpers.ScaleFloat64Proportionally(true, float64(outputNeuron), 0, 1, outcomeMin, outcomeMax)
if (err != nil) { return 0, err }
return outcomeValue, nil
}
//Outputs:
// -int: Layer 1 neuron count (input layer)
// -int: Layer 2 neuron count
@ -631,6 +729,11 @@ func getNeuralNetworkLayerSizes(traitName string)(int, int, int, int, error){
// There are 2 output neurons, each representing a tolerance: Tolerant, Intolerant
return 6, 4, 3, 2, nil
}
case "Height":{
// There are 3000 input neurons
// There is 1 output neuron, representing a height value
return 3000, 2, 2, 1, nil
}
}
return 0, 0, 0, 0, errors.New("getNeuralNetworkLayerSizes called with unknown traitName: " + traitName)
@ -682,7 +785,7 @@ func CreateGeneticPredictionTrainingData_OpenSNP(
userPhenotypeDataObject readBiobankData.PhenotypeData_OpenSNP,
userLocusValuesMap map[int64]locusValue.LocusValue)(bool, []TrainingData, error){
if (traitName != "Eye Color" && traitName != "Lactose Tolerance"){
if (traitName != "Eye Color" && traitName != "Lactose Tolerance" && traitName != "Height"){
return false, nil, errors.New("CreateGeneticPredictionTrainingData_OpenSNP called with unknown traitName: " + traitName)
}
@ -800,6 +903,27 @@ func CreateGeneticPredictionTrainingData_OpenSNP(
return true, []float32{0, 1}, nil
}
case "Height":{
userHeightIsKnown := userPhenotypeDataObject.HeightIsKnown
if (userHeightIsKnown == false){
return false, nil, nil
}
userHeight := userPhenotypeDataObject.Height
// Shortest person of all time: 54 cm
// Tallest person of all time: 272 cm
outputValue, err := helpers.ScaleFloat64Proportionally(true, userHeight, 54, 272, 0, 1)
if (err != nil) { return false, nil, err }
outputValueFloat32 := float32(outputValue)
outputLayer := []float32{outputValueFloat32}
return true, outputLayer, nil
}
}
return false, nil, errors.New("Unknown traitName: " + traitName)
@ -911,55 +1035,49 @@ func CreateGeneticPredictionTrainingData_OpenSNP(
anyLocusExists = true
getLocusAlleles := func()(string, string){
//Outputs:
// -float32: Final neuron value
// -0 == Value is unknown
// -0.5 == Value is known, phase is unknown
// -1 == Value is known, phase is known
// -string: Allele 1 value
// -string: Allele 2 value
getFirstNeuronAndLocusAlleles := func()(float32, string, string){
locusAllele1 := userLocusValue.Base1Value
locusAllele2 := userLocusValue.Base2Value
if (randomizePhaseBool == false){
return locusAllele1, locusAllele2
if (locusAllele1 == locusAllele2){
// Locus phase is unimportant
return 1, locusAllele1, locusAllele2
}
locusIsPhased := userLocusValue.LocusIsPhased
if (randomizePhaseBool == false && locusIsPhased == true){
return 1, locusAllele1, locusAllele2
}
// We randomize the phase of the locus
// We always do this if the locus is not phased, because the genome data might actually be partially/fully phased,
// even if it is not advertised as being phased
randomNumber := pseudorandomNumberGenerator.IntN(2)
if (randomNumber == 1){
// This has a 50% chance of being true.
return locusAllele1, locusAllele2
return 0.5, locusAllele1, locusAllele2
}
return locusAllele2, locusAllele1
return 0.5, locusAllele2, locusAllele1
}
locusAllele1, locusAllele2 := getLocusAlleles()
locusIsKnownAndPhasedNeuronValue, locusAllele1, locusAllele2 := getFirstNeuronAndLocusAlleles()
locusAllele1NeuronValue, err := convertAlleleToNeuron(locusAllele1)
if (err != nil){ return false, nil, err }
locusAllele2NeuronValue, err := convertAlleleToNeuron(locusAllele2)
if (err != nil) { return false, nil, err }
getLocusIsKnownAndPhasedNeuronValue := func()float32{
if (locusAllele1 == locusAllele2){
// Phase of locus must be known.
// Swapping the loci would change nothing.
return 1
}
if (randomizePhaseBool == true){
return 0.5
}
locusIsPhased := userLocusValue.LocusIsPhased
if (locusIsPhased == true){
return 1
}
return 0.5
}
locusIsKnownAndPhasedNeuronValue := getLocusIsKnownAndPhasedNeuronValue()
inputLayer = append(inputLayer, locusIsKnownAndPhasedNeuronValue, locusAllele1NeuronValue, locusAllele2NeuronValue)
}

View file

@ -627,7 +627,7 @@ func StartCreateNewPersonGeneticAnalysis(personIdentifier string)(string, error)
for index, genomeMap := range personGenomesMapList{
newPercentageProgress, err := helpers.ScaleNumberProportionally(true, index, 0, finalIndex, 0, 10)
newPercentageProgress, err := helpers.ScaleIntProportionally(true, index, 0, finalIndex, 0, 10)
if (err != nil) { return err }
err = updatePercentageCompleteFunction(newPercentageProgress)
@ -665,7 +665,7 @@ func StartCreateNewPersonGeneticAnalysis(personIdentifier string)(string, error)
analysisUpdatePercentageCompleteFunction := func(inputProgress int)error{
newPercentageProgress, err := helpers.ScaleNumberProportionally(true, inputProgress, 0, 100, 10, 10)
newPercentageProgress, err := helpers.ScaleIntProportionally(true, inputProgress, 0, 100, 10, 10)
if (err != nil) { return err }
err = updatePercentageCompleteFunction(newPercentageProgress)
@ -899,7 +899,7 @@ func StartCreateNewCoupleGeneticAnalysis(inputPerson1Identifier string, inputPer
break
}
personPercentageComplete, err := helpers.ScaleNumberProportionally(true, processPercentageComplete, 0, 100, personPercentageRangeStart, personPercentageRangeEnd)
personPercentageComplete, err := helpers.ScaleIntProportionally(true, processPercentageComplete, 0, 100, personPercentageRangeStart, personPercentageRangeEnd)
if (err != nil) { return err }
err = updatePercentageCompleteFunction(personPercentageComplete)
@ -978,7 +978,7 @@ func StartCreateNewCoupleGeneticAnalysis(inputPerson1Identifier string, inputPer
updateCoupleAnalysisPercentageCompleteFunction := func(newPercentage int)error{
personPercentageComplete, err := helpers.ScaleNumberProportionally(true, newPercentage, 0, 100, 74, 100)
personPercentageComplete, err := helpers.ScaleIntProportionally(true, newPercentage, 0, 100, 74, 100)
if (err != nil) { return err }
err = updatePercentageCompleteFunction(personPercentageComplete)

View file

@ -68,16 +68,17 @@ func CreateRawGenomeWithMetadataObject(genomeIdentifier [16]byte, rawGenomeStrin
// -[]RawGenomeWithMetadata
// -func(int)error: Update Percentage Complete Function
//Outputs:
// -bool: Any useful locations exist in any of the provided genomes
// -[]GenomeWithMetadata: Genomes with metadata list
// -[][16]byte: All raw genome identifiers list (not including combined genomes)
// -bool: Combined genomes exist
// -[16]byte: Only exclude conflicts genome identifier
// -[16]byte: Only include shared genome identifier
// -error
func GetGenomesWithMetadataListFromRawGenomesList(inputGenomesList []RawGenomeWithMetadata, updatePercentageCompleteFunction func(int)error)([]GenomeWithMetadata, [][16]byte, bool, [16]byte, [16]byte, error){
func GetGenomesWithMetadataListFromRawGenomesList(inputGenomesList []RawGenomeWithMetadata, updatePercentageCompleteFunction func(int)error)(bool, []GenomeWithMetadata, [][16]byte, bool, [16]byte, [16]byte, error){
if (len(inputGenomesList) == 0){
return nil, nil, false, [16]byte{}, [16]byte{}, errors.New("GetGenomesWithMetadataListFromRawGenomesList called with empty inputGenomesList")
return false, nil, nil, false, [16]byte{}, [16]byte{}, errors.New("GetGenomesWithMetadataListFromRawGenomesList called with empty inputGenomesList")
}
// The reading of genomes will take up the first 20% of the percentage range
@ -87,18 +88,17 @@ func GetGenomesWithMetadataListFromRawGenomesList(inputGenomesList []RawGenomeWi
// Each map stores a genome from a company or a combined genome.
genomesWithMetadataList := make([]GenomeWithMetadata, 0)
numberOfGenomesRead := 0
totalNumberOfGenomesToRead := len(inputGenomesList)
finalIndex := len(inputGenomesList) - 1
allRawGenomeIdentifiersList := make([][16]byte, 0)
for _, rawGenomeWithMetadataObject := range inputGenomesList{
for index, rawGenomeWithMetadataObject := range inputGenomesList{
newPercentageCompletion, err := helpers.ScaleNumberProportionally(true, numberOfGenomesRead, 0, totalNumberOfGenomesToRead, 0, 20)
if (err != nil) { return nil, nil, false, [16]byte{}, [16]byte{}, err }
newPercentageCompletion, err := helpers.ScaleIntProportionally(true, index, 0, finalIndex, 0, 20)
if (err != nil) { return false, nil, nil, false, [16]byte{}, [16]byte{}, err }
err = updatePercentageCompleteFunction(newPercentageCompletion)
if (err != nil) { return nil, nil, false, [16]byte{}, [16]byte{}, err }
if (err != nil) { return false, nil, nil, false, [16]byte{}, [16]byte{}, err }
genomeIdentifier := rawGenomeWithMetadataObject.GenomeIdentifier
genomeIsPhased := rawGenomeWithMetadataObject.GenomeIsPhased
@ -107,12 +107,11 @@ func GetGenomesWithMetadataListFromRawGenomesList(inputGenomesList []RawGenomeWi
// Now we convert rawGenomeMap to a genomeMap
anyValuesExist, genomeMap, err := ConvertRawGenomeToGenomeMap(rawGenomeMap, genomeIsPhased)
if (err != nil) { return nil, nil, false, [16]byte{}, [16]byte{}, err }
if (err != nil) { return false, nil, nil, false, [16]byte{}, [16]byte{}, err }
if (anyValuesExist == false){
// We have to make sure this never happens so the user isn't confused as to why genomes
// that were imported were not included in the analysis
// We make sure this doesn't happen by verifying the genome at the time of importing
return nil, nil, false, [16]byte{}, [16]byte{}, errors.New("Genome supplied to GetGenomesWithMetadataListFromRawGenomesList has no valid locations.")
// This genome is not useful
// No useful locations exist
continue
}
genomeWithMetadataObject := GenomeWithMetadata{
@ -123,17 +122,20 @@ func GetGenomesWithMetadataListFromRawGenomesList(inputGenomesList []RawGenomeWi
genomesWithMetadataList = append(genomesWithMetadataList, genomeWithMetadataObject)
allRawGenomeIdentifiersList = append(allRawGenomeIdentifiersList, genomeIdentifier)
}
numberOfGenomesRead += 1
if (len(genomesWithMetadataList) == 0){
// None of the provided genomes contained any useful locations
return false, nil, nil, false, [16]byte{}, [16]byte{}, nil
}
containsDuplicates, _ := helpers.CheckIfListContainsDuplicates(allRawGenomeIdentifiersList)
if (containsDuplicates == true){
return nil, nil, false, [16]byte{}, [16]byte{}, errors.New("GetGenomesWithMetadataListFromRawGenomesList called with inputGenomesList containing duplicate genomeIdentifiers.")
return false, nil, nil, false, [16]byte{}, [16]byte{}, errors.New("GetGenomesWithMetadataListFromRawGenomesList called with inputGenomesList containing duplicate genomeIdentifiers.")
}
err := updatePercentageCompleteFunction(20)
if (err != nil){ return nil, nil, false, [16]byte{}, [16]byte{}, err }
if (err != nil){ return false, nil, nil, false, [16]byte{}, [16]byte{}, err }
if (len(genomesWithMetadataList) <= 1){
@ -141,9 +143,9 @@ func GetGenomesWithMetadataListFromRawGenomesList(inputGenomesList []RawGenomeWi
// No genome combining is needed.
err = updatePercentageCompleteFunction(100)
if (err != nil){ return nil, nil, false, [16]byte{}, [16]byte{}, err }
if (err != nil){ return false, nil, nil, false, [16]byte{}, [16]byte{}, err }
return genomesWithMetadataList, allRawGenomeIdentifiersList, false, [16]byte{}, [16]byte{}, nil
return true, genomesWithMetadataList, allRawGenomeIdentifiersList, false, [16]byte{}, [16]byte{}, nil
}
// Now we create the shared genomes
@ -156,15 +158,15 @@ func GetGenomesWithMetadataListFromRawGenomesList(inputGenomesList []RawGenomeWi
// This map stores all RSIDs across all genomes
allRSIDsMap := make(map[int64]struct{})
finalIndex := len(genomesWithMetadataList) - 1
finalIndex = len(genomesWithMetadataList) - 1
for index, genomeWithMetadataObject := range genomesWithMetadataList{
newPercentageCompletion, err := helpers.ScaleNumberProportionally(true, index, 0, finalIndex, 20, 50)
if (err != nil){ return nil, nil, false, [16]byte{}, [16]byte{}, err }
newPercentageCompletion, err := helpers.ScaleIntProportionally(true, index, 0, finalIndex, 20, 50)
if (err != nil){ return false, nil, nil, false, [16]byte{}, [16]byte{}, err }
err = updatePercentageCompleteFunction(newPercentageCompletion)
if (err != nil){ return nil, nil, false, [16]byte{}, [16]byte{}, err }
if (err != nil){ return false, nil, nil, false, [16]byte{}, [16]byte{}, err }
genomeMap := genomeWithMetadataObject.GenomeMap
@ -185,11 +187,11 @@ func GetGenomesWithMetadataListFromRawGenomesList(inputGenomesList []RawGenomeWi
for rsID, _ := range allRSIDsMap{
newPercentageCompletion, err := helpers.ScaleNumberProportionally(true, index, 0, finalIndex, 50, 100)
if (err != nil){ return nil, nil, false, [16]byte{}, [16]byte{}, err }
newPercentageCompletion, err := helpers.ScaleIntProportionally(true, index, 0, finalIndex, 50, 100)
if (err != nil){ return false, nil, nil, false, [16]byte{}, [16]byte{}, err }
err = updatePercentageCompleteFunction(newPercentageCompletion)
if (err != nil){ return nil, nil, false, [16]byte{}, [16]byte{}, err }
if (err != nil){ return false, nil, nil, false, [16]byte{}, [16]byte{}, err }
index += 1
@ -200,7 +202,7 @@ func GetGenomesWithMetadataListFromRawGenomesList(inputGenomesList []RawGenomeWi
}
anyAliasesExist, rsidAliasesList, err := locusMetadata.GetRSIDAliases(rsID)
if (err != nil){ return nil, nil, false, [16]byte{}, [16]byte{}, err }
if (err != nil){ return false, nil, nil, false, [16]byte{}, [16]byte{}, err }
if (anyAliasesExist == true){
for _, rsidAlias := range rsidAliasesList{
@ -386,7 +388,7 @@ func GetGenomesWithMetadataListFromRawGenomesList(inputGenomesList []RawGenomeWi
}
locusBase1, locusBase2, phaseIsKnown_OnlyExcludeConflicts, phaseIsKnown_OnlyIncludeShared, err := getLocusBasePair()
if (err != nil){ return nil, nil, false, [16]byte{}, [16]byte{}, err }
if (err != nil){ return false, nil, nil, false, [16]byte{}, [16]byte{}, err }
// Now we add to the combined genome maps
// The OnlyExcludeConflicts will only omit when there is a tie
@ -436,7 +438,7 @@ func GetGenomesWithMetadataListFromRawGenomesList(inputGenomesList []RawGenomeWi
}
onlyExcludeConflictsGenomeIdentifier, err := helpers.GetNewRandom16ByteArray()
if (err != nil) { return nil, nil, false, [16]byte{}, [16]byte{}, err }
if (err != nil) { return false, nil, nil, false, [16]byte{}, [16]byte{}, err }
onlyExcludeConflictsGenomeWithMetadataObject := GenomeWithMetadata{
GenomeType: "OnlyExcludeConflicts",
@ -445,7 +447,7 @@ func GetGenomesWithMetadataListFromRawGenomesList(inputGenomesList []RawGenomeWi
}
onlyIncludeSharedGenomeIdentifier, err := helpers.GetNewRandom16ByteArray()
if (err != nil) { return nil, nil, false, [16]byte{}, [16]byte{}, err }
if (err != nil) { return false, nil, nil, false, [16]byte{}, [16]byte{}, err }
onlyIncludeSharedGenomeWithMetadataObject := GenomeWithMetadata{
GenomeType: "OnlyIncludeShared",
@ -456,9 +458,9 @@ func GetGenomesWithMetadataListFromRawGenomesList(inputGenomesList []RawGenomeWi
genomesWithMetadataList = append(genomesWithMetadataList, onlyExcludeConflictsGenomeWithMetadataObject, onlyIncludeSharedGenomeWithMetadataObject)
err = updatePercentageCompleteFunction(100)
if (err != nil){ return nil, nil, false, [16]byte{}, [16]byte{}, err }
if (err != nil){ return false, nil, nil, false, [16]byte{}, [16]byte{}, err }
return genomesWithMetadataList, allRawGenomeIdentifiersList, true, onlyExcludeConflictsGenomeIdentifier, onlyIncludeSharedGenomeIdentifier, nil
return true, genomesWithMetadataList, allRawGenomeIdentifiersList, true, onlyExcludeConflictsGenomeIdentifier, onlyIncludeSharedGenomeIdentifier, nil
}
//Outputs:

View file

@ -7,6 +7,7 @@ import "seekia/internal/helpers"
import "encoding/csv"
import "os"
import "io"
import "strings"
type PhenotypeData_OpenSNP struct{
@ -237,7 +238,7 @@ func ReadOpenSNPPhenotypesFile(fileObject *os.File)(bool, []PhenotypeData_OpenSN
// -int: User height (in centimeters)
getUserHeight := func()(bool, float64){
userHeightRaw := userDataLineSlice[13]
userHeightRaw := userDataLineSlice[13]
switch userHeightRaw{
case "-":{
@ -273,64 +274,88 @@ func ReadOpenSNPPhenotypesFile(fileObject *os.File)(bool, []PhenotypeData_OpenSN
case `4'9"`:{
return true, 144.78
}
case `4'10"`:{
case `4'10"`, `4'10`:{
return true, 147.32
}
case `4'11"`:{
case `4'11"`, `4' 11"`:{
return true, 149.86
}
case `5'`:{
case `5'`, `5' 0"`, `5' 0'`, `5'0"`, `5'0`:{
return true, 152.4
}
case `5'1"`:{
case `5'1"`, `5'1" or 155cm`:{
return true, 154.94
}
case `5'1.5"`:{
return true, 156.21
}
case `5'2"`:{
return true, 157.48
}
case `5'3"`, `5'3''`, `160 cm`:{
case `5' 2 1/2"`:{
return true, 158.75
}
case `5'2.75" at max`:{
return true, 159.385
}
case `5'3"`, `5'3''`:{
return true, 160
}
case `5'4"`:{
case `5' 3.5" `:{
return true, 161.29
}
case `5'4"`, `5'4`, `5'4''`, "162.56", `64"`:{
return true, 162.56
}
case `5'5"`:{
case `5'4 3/4, taller than all females in my family`:{
return true, 164.465
}
case `5'5"`, `5' 5"`, `5'5" `, `55”`:{
return true, 165.1
}
case `5'6"`:{
case "167":{
return true, 167
}
case `5'6"`, `56`:{
return true, 167.64
}
case `168 cm`:{
return true, 168
case `5' 6.5" `:{
return true, 168.91
}
case `169.316`:{
return true, 169.316
}
case `5'7"`:{
return true, 170.18
}
case `Average ( 165cm < x < 180cm )`:{
return true, 172.5
}
case `5'8"`:{
return true, 172.72
}
case `5'9"`:{
case `Average 173cm`:{
return true, 173
}
case `5'8.5"`:{
return true, 173.99
}
case `5'9"`, `5'9"/176cm`:{
return true, 175.26
}
case `5" 9 1/2"`:{
return true, 176.53
}
case `5'10"`, `5'10''`:{
return true, 177.8
}
case `179 cm`:{
return true, 179
}
case `180cm`:{
return true, 180
}
case `5'11"`:{
return true, 180.34
}
case `6'`:{
case `6'`, `6 ft 0 in`, `6'0" - 183cm`:{
return true, 182.88
}
case `183 cm`:{
return true, 183
}
case `6'1"`:{
case `6'1"`, `6' 1" - 185 cm`:{
return true, 185.42
}
case `6'2"`:{
@ -339,6 +364,9 @@ func ReadOpenSNPPhenotypesFile(fileObject *os.File)(bool, []PhenotypeData_OpenSN
case `6'3"`:{
return true, 190.5
}
case "192":{
return true, 192
}
case `6'4"`:{
return true, 193.04
}
@ -348,6 +376,9 @@ func ReadOpenSNPPhenotypesFile(fileObject *os.File)(bool, []PhenotypeData_OpenSN
case `6'6"`:{
return true, 198.12
}
case `>200cm`:{
return true, 200
}
case `6'7"`:{
return true, 200.66
}
@ -366,8 +397,28 @@ func ReadOpenSNPPhenotypesFile(fileObject *os.File)(bool, []PhenotypeData_OpenSN
case `7'`:{
return true, 213.36
}
}
//TODO: Add more responses
trimmedHeight, suffixExists := strings.CutSuffix(userHeightRaw, "cm")
if (suffixExists == true){
heightFloat64, err := helpers.ConvertStringToFloat64(trimmedHeight)
if (err == nil){
return true, heightFloat64
}
}
trimmedHeight, suffixExists = strings.CutSuffix(userHeightRaw, " cm")
if (suffixExists == true){
heightFloat64, err := helpers.ConvertStringToFloat64(trimmedHeight)
if (err == nil){
return true, heightFloat64
}
}
// This is an outcome with backticks and quotes
result := "6`" + `2"`
if (userHeightRaw == result){
return true, 187.96
}
return false, 0

View file

@ -1948,29 +1948,29 @@ func ConvertFloat64ToRoundedStringWithTranslatedUnits(inputFloat float64)(string
}
// This function takes a number and the min and max range of that number
// It returns a number scaled between a new min and max
func ScaleNumberProportionally(ascending bool, input int, inputMin int, inputMax int, newMin int, newMax int)(int, error){
// This function takes an int and the min and max range of that int
// It returns an int scaled between a new min and max
func ScaleIntProportionally(ascending bool, input int, inputMin int, inputMax int, newMin int, newMax int)(int, error){
if (inputMin == inputMax) {
return inputMin, nil
}
if (inputMin > inputMax) {
return 0, errors.New("ScaleNumberProportionally error: InputMin is greater than inputMax")
return 0, errors.New("ScaleIntProportionally error: InputMin is greater than inputMax")
}
if (input < inputMin) {
return 0, errors.New("ScaleNumberProportionally error: Input is less than inputMin")
return 0, errors.New("ScaleIntProportionally error: Input is less than inputMin")
}
if (input > inputMax) {
return 0, errors.New("ScaleNumberProportionally error: Input is greater than inputMax")
return 0, errors.New("ScaleIntProportionally error: Input is greater than inputMax")
}
if (newMin == newMax) {
return newMin, nil
}
if (newMin > newMin){
return 0, errors.New("ScaleNumberProportionally error: newMin is greater than newMin.")
return 0, errors.New("ScaleIntProportionally error: newMin is greater than newMin.")
}
inputRangePortionLength := input - inputMin
@ -2002,6 +2002,58 @@ func ScaleNumberProportionally(ascending bool, input int, inputMin int, inputMax
return result, nil
}
// This function takes an float64 and the min and max range of that float64
// It returns a float64 scaled between a new min and max
func ScaleFloat64Proportionally(ascending bool, input float64, inputMin float64, inputMax float64, newMin float64, newMax float64)(float64, error){
if (inputMin == inputMax) {
return inputMin, nil
}
if (inputMin > inputMax) {
return 0, errors.New("ScaleFloat64Proportionally error: InputMin is greater than inputMax")
}
if (input < inputMin) {
return 0, errors.New("ScaleFloat64Proportionally error: Input is less than inputMin")
}
if (input > inputMax) {
return 0, errors.New("ScaleFloat64Proportionally error: Input is greater than inputMax")
}
if (newMin == newMax) {
return newMin, nil
}
if (newMin > newMin){
return 0, errors.New("ScaleFloat64Proportionally error: newMin is greater than newMin.")
}
inputRangePortionLength := input - inputMin
inputRangeDistance := inputMax - inputMin
inputRangePortion := inputRangePortionLength/inputRangeDistance
// This represents the portion of our output range that we want to travel across
getOutputRangePortion := func()float64{
if (ascending == true){
return inputRangePortion
}
outputRangePortion := 1 - inputRangePortion
return outputRangePortion
}
outputRangePortion := getOutputRangePortion()
outputRangeDistance := newMax - newMin
outputRangePortionLength := outputRangeDistance * outputRangePortion
result := newMin + outputRangePortionLength
return result, nil
}
func XORTwo32ByteArrays(array1 [32]byte, array2 [32]byte)[32]byte{
var newArray [32]byte

View file

@ -321,86 +321,86 @@ func TestSliceFunctions(t *testing.T){
func TestNumberProportionalScaling(t *testing.T){
result, err := helpers.ScaleNumberProportionally(true, 50, 0, 100, 0, 50)
result, err := helpers.ScaleIntProportionally(true, 50, 0, 100, 0, 50)
if (err != nil){
t.Fatalf("ScaleNumberProportionally failed: " + err.Error())
t.Fatalf("ScaleIntProportionally failed: " + err.Error())
}
if (result != 25){
t.Fatalf("ScaleNumberProportionally failed test 1.")
t.Fatalf("ScaleIntProportionally failed test 1.")
}
result, err = helpers.ScaleNumberProportionally(true, 25, 0, 100, 0, 200)
result, err = helpers.ScaleIntProportionally(true, 25, 0, 100, 0, 200)
if (err != nil){
t.Fatalf("ScaleNumberProportionally failed: " + err.Error())
t.Fatalf("ScaleIntProportionally failed: " + err.Error())
}
if (result != 50){
t.Fatalf("ScaleNumberProportionally failed test 2.")
t.Fatalf("ScaleIntProportionally failed test 2.")
}
result, err = helpers.ScaleNumberProportionally(false, 25, 0, 100, 0, 200)
result, err = helpers.ScaleIntProportionally(false, 25, 0, 100, 0, 200)
if (err != nil){
t.Fatalf("ScaleNumberProportionally failed: " + err.Error())
t.Fatalf("ScaleIntProportionally failed: " + err.Error())
}
if (result != 150){
t.Fatalf("ScaleNumberProportionally failed test 3.")
t.Fatalf("ScaleIntProportionally failed test 3.")
}
result, err = helpers.ScaleNumberProportionally(true, 1, 0, 10, 0, 200)
result, err = helpers.ScaleIntProportionally(true, 1, 0, 10, 0, 200)
if (err != nil){
t.Fatalf("ScaleNumberProportionally failed: " + err.Error())
t.Fatalf("ScaleIntProportionally failed: " + err.Error())
}
if (result != 20){
t.Fatalf("ScaleNumberProportionally failed test 4.")
t.Fatalf("ScaleIntProportionally failed test 4.")
}
result, err = helpers.ScaleNumberProportionally(true, -50, -100, 0, 0, 200)
result, err = helpers.ScaleIntProportionally(true, -50, -100, 0, 0, 200)
if (err != nil){
t.Fatalf("ScaleNumberProportionally failed: " + err.Error())
t.Fatalf("ScaleIntProportionally failed: " + err.Error())
}
if (result != 100){
t.Fatalf("ScaleNumberProportionally failed test 5.")
t.Fatalf("ScaleIntProportionally failed test 5.")
}
result, err = helpers.ScaleNumberProportionally(true, -25, -100, 0, 0, 200)
result, err = helpers.ScaleIntProportionally(true, -25, -100, 0, 0, 200)
if (err != nil){
t.Fatalf("ScaleNumberProportionally failed: " + err.Error())
t.Fatalf("ScaleIntProportionally failed: " + err.Error())
}
if (result != 150){
t.Fatalf("ScaleNumberProportionally failed test 6.")
t.Fatalf("ScaleIntProportionally failed test 6.")
}
result, err = helpers.ScaleNumberProportionally(true, 50, 0, 100, 0, 2)
result, err = helpers.ScaleIntProportionally(true, 50, 0, 100, 0, 2)
if (err != nil){
t.Fatalf("ScaleNumberProportionally failed: " + err.Error())
t.Fatalf("ScaleIntProportionally failed: " + err.Error())
}
if (result != 1){
t.Fatalf("ScaleNumberProportionally failed test 7.")
t.Fatalf("ScaleIntProportionally failed test 7.")
}
result, err = helpers.ScaleNumberProportionally(true, 10, 0, 100, 5, 25)
result, err = helpers.ScaleIntProportionally(true, 10, 0, 100, 5, 25)
if (err != nil){
t.Fatalf("ScaleNumberProportionally failed: " + err.Error())
t.Fatalf("ScaleIntProportionally failed: " + err.Error())
}
if (result != 7){
t.Fatalf("ScaleNumberProportionally failed test 8.")
t.Fatalf("ScaleIntProportionally failed test 8.")
}
result, err = helpers.ScaleNumberProportionally(true, 100, 0, 100, 2, 22)
result, err = helpers.ScaleIntProportionally(true, 100, 0, 100, 2, 22)
if (err != nil){
t.Fatalf("ScaleNumberProportionally failed: " + err.Error())
t.Fatalf("ScaleIntProportionally failed: " + err.Error())
}
if (result != 22){
t.Fatalf("ScaleNumberProportionally failed test 9.")
t.Fatalf("ScaleIntProportionally failed test 9.")
}
result, err = helpers.ScaleNumberProportionally(false, 0, 0, 100, 2, 22)
result, err = helpers.ScaleIntProportionally(false, 0, 0, 100, 2, 22)
if (err != nil){
t.Fatalf("ScaleNumberProportionally failed: " + err.Error())
t.Fatalf("ScaleIntProportionally failed: " + err.Error())
}
if (result != 22){
t.Fatalf("ScaleNumberProportionally failed test 10.")
t.Fatalf("ScaleIntProportionally failed test 10.")
}
}

View file

@ -121,7 +121,7 @@ func GetImageWithEmojiOverlay(inputImage image.Image, emojiImage image.Image, em
imageLongerSideLength := max(imageWidth, imageHeight)
// This is the length of the longest side of the emoji we will draw
emojiLongerSideMaximumLength, err := helpers.ScaleNumberProportionally(true, emojiScale, 0, 100, 1, imageLongerSideLength)
emojiLongerSideMaximumLength, err := helpers.ScaleIntProportionally(true, emojiScale, 0, 100, 1, imageLongerSideLength)
if (err != nil) { return nil, err }
newEmoji, err := imagery.ResizeGolangImage(emojiImage, emojiLongerSideMaximumLength)
@ -130,10 +130,10 @@ func GetImageWithEmojiOverlay(inputImage image.Image, emojiImage image.Image, em
// Now we get the X and Y Coordinate point for where we will draw the emoji
// We first find the center coordinate of the emoji we are drawing
emojiCenterXCoordinate, err := helpers.ScaleNumberProportionally(true, xAxisPercentage, 0, 100, 0, imageWidth)
emojiCenterXCoordinate, err := helpers.ScaleIntProportionally(true, xAxisPercentage, 0, 100, 0, imageWidth)
if (err != nil) { return nil, err }
emojiCenterYCoordinate, err := helpers.ScaleNumberProportionally(false, yAxisPercentage, 0, 100, 0, imageHeight)
emojiCenterYCoordinate, err := helpers.ScaleIntProportionally(false, yAxisPercentage, 0, 100, 0, imageHeight)
if (err != nil) { return nil, err }
emojiWidth, emojiHeight, err := imagery.GetImageWidthAndHeightPixels(newEmoji)

View file

@ -497,7 +497,7 @@ func PixelateGolangImage(inputImage image.Image, amount0to100 int)(image.Image,
longerSideLength := max(widthPixels, heightPixels)
pixelationAmountInt, err := helpers.ScaleNumberProportionally(true, amount0to100, 0, 100, 0, longerSideLength/5)
pixelationAmountInt, err := helpers.ScaleIntProportionally(true, amount0to100, 0, 100, 0, longerSideLength/5)
if (err != nil) { return nil, err }
rectangle := image.Rect(0, 0, widthPixels, heightPixels)

View file

@ -469,7 +469,7 @@ func StartUpdatingMyConversations(identityType string, networkType byte) error{
// Input is a value between 0-100
// We reduce it down to a value between 0-50
newProgressInt, err := helpers.ScaleNumberProportionally(true, input, 0, 100, 0, 50)
newProgressInt, err := helpers.ScaleIntProportionally(true, input, 0, 100, 0, 50)
if (err != nil) { return err }
newPercentageProgressFloat := float64(newProgressInt)/100
@ -715,7 +715,7 @@ func StartUpdatingMyConversations(identityType string, networkType byte) error{
return nil
}
newScaledPercentageInt, err := helpers.ScaleNumberProportionally(true, index, 0, maximumIndex, 70, 85)
newScaledPercentageInt, err := helpers.ScaleIntProportionally(true, index, 0, maximumIndex, 70, 85)
if (err != nil) { return err }
newProgressFloat := float64(newScaledPercentageInt)/100

View file

@ -268,7 +268,7 @@ func GetUpdatedMyChatMessagesMapList(myIdentityType string, networkType byte, up
for messageHash, messageInbox := range myRawInboxMessageHashesMap{
newPercentageProgress, err := helpers.ScaleNumberProportionally(true, index, 0, maximumIndex, 20, 100)
newPercentageProgress, err := helpers.ScaleIntProportionally(true, index, 0, maximumIndex, 20, 100)
if (err != nil){ return err }
err = updateProgressFunction(newPercentageProgress)

View file

@ -480,7 +480,7 @@ func StartUpdatingViewedContent(networkType byte)error{
return nil
}
newScaledPercentageInt, err := helpers.ScaleNumberProportionally(true, index, 0, maximumIndex, 50, 80)
newScaledPercentageInt, err := helpers.ScaleIntProportionally(true, index, 0, maximumIndex, 50, 80)
if (err != nil) { return err }
newProgressFloat := float64(newScaledPercentageInt)/100

View file

@ -365,7 +365,7 @@ func StartUpdatingViewedModerators(networkType byte)error{
return nil
}
progressPercentage, err := helpers.ScaleNumberProportionally(true, index, 0, numberOfModerators-1, 20, 50)
progressPercentage, err := helpers.ScaleIntProportionally(true, index, 0, numberOfModerators-1, 20, 50)
if (err != nil) { return err }
progressFloat := float64(progressPercentage)/100
@ -452,7 +452,7 @@ func StartUpdatingViewedModerators(networkType byte)error{
return nil
}
newScaledPercentageInt, err := helpers.ScaleNumberProportionally(true, index, 0, maximumIndex, 50, 80)
newScaledPercentageInt, err := helpers.ScaleIntProportionally(true, index, 0, maximumIndex, 50, 80)
if (err != nil) { return err }
newProgressFloat := float64(newScaledPercentageInt)/100

View file

@ -323,7 +323,7 @@ func StartUpdatingMyMatches(networkType byte)error{
return nil
}
progressPercentage, err := helpers.ScaleNumberProportionally(true, index, 0, maximumIndex, 0, 50)
progressPercentage, err := helpers.ScaleIntProportionally(true, index, 0, maximumIndex, 0, 50)
if (err != nil) { return err }
progressFloat := float64(progressPercentage)/100
@ -449,7 +449,7 @@ func StartUpdatingMyMatches(networkType byte)error{
return nil
}
newScaledPercentageInt, err := helpers.ScaleNumberProportionally(true, index, 0, maximumIndex, 50, 80)
newScaledPercentageInt, err := helpers.ScaleIntProportionally(true, index, 0, maximumIndex, 50, 80)
if (err != nil) { return err }
newProgressFloat := float64(newScaledPercentageInt)/100

View file

@ -335,7 +335,7 @@ func StartUpdatingViewedHosts(networkType byte)error{
return nil
}
progressPercentage, err := helpers.ScaleNumberProportionally(true, index, 0, numberOfEnabledHosts-1, 0, 50)
progressPercentage, err := helpers.ScaleIntProportionally(true, index, 0, numberOfEnabledHosts-1, 0, 50)
if (err != nil) { return err }
progressFloat := float64(progressPercentage)/100
@ -413,13 +413,13 @@ func StartUpdatingViewedHosts(networkType byte)error{
hostAttributeValuesMap[hostIdentityHashString] = attributeValueFloat
}
isStopped := CheckIfBuildViewedHostsIsStopped()
if (isStopped == true){
return nil
}
newScaledPercentageInt, err := helpers.ScaleNumberProportionally(true, index, 0, maximumIndex, 50, 80)
newScaledPercentageInt, err := helpers.ScaleIntProportionally(true, index, 0, maximumIndex, 50, 80)
if (err != nil) { return err }
newProgressFloat := float64(newScaledPercentageInt)/100
@ -429,47 +429,47 @@ func StartUpdatingViewedHosts(networkType byte)error{
appMemory.SetMemoryEntry("ViewedHostsReadyProgressStatus", newProgressString)
}
compareHostsFunction := func(identityHashA string, identityHashB string)int{
compareHostsFunction := func(identityHash1 string, identityHash2 string)int{
if (identityHashA == identityHashB){
if (identityHash1 == identityHash2){
panic("compareHostsFunction called with duplicate hosts.")
}
attributeValueA, attributeValueAExists := hostAttributeValuesMap[identityHashA]
attributeValue1, attributeValue1Exists := hostAttributeValuesMap[identityHash1]
attributeValueB, attributeValueBExists := hostAttributeValuesMap[identityHashB]
attributeValue2, attributeValue2Exists := hostAttributeValuesMap[identityHash2]
if (attributeValueAExists == false && attributeValueBExists == false){
if (attributeValue1Exists == false && attributeValue2Exists == false){
// We don't know the attribute value for either host
// We sort hosts in unicode order
if (identityHashA < identityHashB){
if (identityHash1 < identityHash2){
return -1
}
return 1
} else if (attributeValueAExists == true && attributeValueBExists == false){
} else if (attributeValue1Exists == true && attributeValue2Exists == false){
// We sort unknown attribute hosts to the back of the list
return -1
} else if (attributeValueAExists == false && attributeValueBExists == true){
} else if (attributeValue1Exists == false && attributeValue2Exists == true){
return 1
}
// Both attribute values exist
if (attributeValueA == attributeValueB){
if (attributeValue1 == attributeValue2){
// We sort identity hashes in unicode order
if (identityHashA < identityHashB){
if (identityHash1 < identityHash2){
return -1
}
return 1
}
if (attributeValueA < attributeValueB){
if (attributeValue1 < attributeValue2){
if (currentSortDirection == "Ascending"){
return -1

View file

@ -153,11 +153,6 @@ func UpdateMyExportedProfile(myProfileType string, networkType byte)error{
_, _, _, _, myGenomesMap, err := readGeneticAnalysis.GetMetadataFromPersonGeneticAnalysis(myGeneticAnalysisObject)
if (err != nil) { return err }
myGenomeLocusValuesMap, exists := myGenomesMap[genomeIdentifierToShare]
if (exists == false){
return errors.New("GetMyChosenMateGeneticAnalysis returning genetic analysis which has GenomesMap which is missing my genome identifier.")
}
monogenicDiseaseNamesList, err := monogenicDiseases.GetMonogenicDiseaseNamesList()
if (err != nil) { return err }
@ -253,6 +248,11 @@ func UpdateMyExportedProfile(myProfileType string, networkType byte)error{
}
}
myGenomeLocusValuesMap, exists := myGenomesMap[genomeIdentifierToShare]
if (exists == false){
return errors.New("GetMyChosenMateGeneticAnalysis returning genetic analysis which has GenomesMap which is missing my genome identifier.")
}
for rsID, _ := range myLociToShareMap{
locusValueObject, exists := myGenomeLocusValuesMap[rsID]

View file

@ -38,6 +38,7 @@ import "os"
import "strings"
import "sync"
import "slices"
import "math"
import mathRand "math/rand/v2"
import goFilepath "path/filepath"
import "time"
@ -722,7 +723,7 @@ func setStartAndMonitorCreateTrainingDataPage(window fyne.Window, previousPage f
if (err != nil) { return false, false, err }
//TODO: Add more traits
traitNamesList := []string{"Eye Color", "Lactose Tolerance"}
traitNamesList := []string{"Eye Color", "Lactose Tolerance", "Height"}
// We create the folders for each trait's training data
@ -765,7 +766,7 @@ func setStartAndMonitorCreateTrainingDataPage(window fyne.Window, previousPage f
err = progressDetailsBinding.Set(progressDetailsStatus)
if (err != nil) { return false, false, err }
trainingProgressPercentage, err := helpers.ScaleNumberProportionally(true, index, 0, maximumIndex, 0, 100)
trainingProgressPercentage, err := helpers.ScaleIntProportionally(true, index, 0, maximumIndex, 0, 100)
if (err != nil) { return false, false, err }
trainingProgressFloat64 := float64(trainingProgressPercentage)/100
@ -837,18 +838,26 @@ func setStartAndMonitorCreateTrainingDataPage(window fyne.Window, previousPage f
continue
}
getUserLociValuesMap := func()(map[int64]locusValue.LocusValue, error){
//Outputs:
// -bool: Any useful locations exist in any of the user's genomes
// -map[int64]locusValue.LocusValue
// -error
getUserLociValuesMap := func()(bool, map[int64]locusValue.LocusValue, error){
updatePercentageCompleteFunction := func(_ int)error{
return nil
}
genomesWithMetadataList, _, combinedGenomesExist, onlyExcludeConflictsGenomeIdentifier, _, err := prepareRawGenomes.GetGenomesWithMetadataListFromRawGenomesList(userRawGenomesWithMetadataList, updatePercentageCompleteFunction)
if (err != nil) { return nil, err }
anyUsefulLocationsExist, genomesWithMetadataList, _, combinedGenomesExist, onlyExcludeConflictsGenomeIdentifier, _, err := prepareRawGenomes.GetGenomesWithMetadataListFromRawGenomesList(userRawGenomesWithMetadataList, updatePercentageCompleteFunction)
if (err != nil) { return false, nil, err }
if (anyUsefulLocationsExist == false){
// None of the user's genomes have any useful locations
return false, nil, nil
}
if (combinedGenomesExist == false){
if (len(genomesWithMetadataList) != 1){
return nil, errors.New("GetGenomesWithMetadataListFromRawGenomesList returning non-1 length genomesWithMetadataList when combinedGenomesExist == false")
return false, nil, errors.New("GetGenomesWithMetadataListFromRawGenomesList returning non-1 length genomesWithMetadataList when combinedGenomesExist == false")
}
// Only 1 genome exists
@ -857,7 +866,7 @@ func setStartAndMonitorCreateTrainingDataPage(window fyne.Window, previousPage f
genomeMap := genomeWithMetadataObject.GenomeMap
return genomeMap, nil
return true, genomeMap, nil
}
for _, genomeWithMetadataObject := range genomesWithMetadataList{
@ -868,15 +877,19 @@ func setStartAndMonitorCreateTrainingDataPage(window fyne.Window, previousPage f
genomeMap := genomeWithMetadataObject.GenomeMap
return genomeMap, nil
return true, genomeMap, nil
}
}
return nil, errors.New("OnlyExcludeConflicts genome not found from GetGenomesWithMetadataListFromRawGenomesList's returned list.")
return false, nil, errors.New("OnlyExcludeConflicts genome not found from GetGenomesWithMetadataListFromRawGenomesList's returned list.")
}
userLociValuesMap, err := getUserLociValuesMap()
anyUsefulLocationsExist, userLociValuesMap, err := getUserLociValuesMap()
if (err != nil) { return false, false, err }
if (anyUsefulLocationsExist == false){
// None of the user's genome files contain any useful locations
continue
}
for _, traitName := range traitNamesList{
@ -980,7 +993,7 @@ func setTrainModelsPage(window fyne.Window, previousPage func()){
description3 := getLabelCentered("This will take a while.")
description4 := getLabelCentered("You must select a trait model to train.")
traitNamesList := []string{"Eye Color", "Lactose Tolerance"}
traitNamesList := []string{"Eye Color", "Lactose Tolerance", "Height"}
traitNameSelector := widget.NewSelect(traitNamesList, nil)
@ -1305,7 +1318,7 @@ func setTestModelsPage(window fyne.Window, previousPage func()){
description5 := getLabelCentered("The results will also be saved in the ModelAccuracies folder.")
description6 := getLabelCentered("You must select a trait model to test.")
traitNamesList := []string{"Eye Color", "Lactose Tolerance"}
traitNamesList := []string{"Eye Color", "Lactose Tolerance", "Height"}
traitNameSelector := widget.NewSelect(traitNamesList, nil)
@ -1383,7 +1396,7 @@ func setStartAndMonitorTestModelPage(window fyne.Window, traitName string, previ
//Outputs:
// -bool: Process completed (true == was not stopped mid-way)
// -geneticPrediction.TraitPredictionAccuracyInfoMap
// -geneticPrediction.DiscreteTraitPredictionAccuracyInfoMap
// -error
testModel := func()(bool, geneticPrediction.DiscreteTraitPredictionAccuracyInfoMap, error){
@ -1464,10 +1477,10 @@ func setStartAndMonitorTestModelPage(window fyne.Window, traitName string, previ
return false, nil, errors.New("Neural network prediction output length does not match expected output length.")
}
correctOutcomeName, err := geneticPrediction.GetOutcomeNameFromOutputLayer(traitName, true, trainingDataExpectedOutputLayer)
correctOutcomeName, err := geneticPrediction.GetDiscreteOutcomeNameFromOutputLayer(traitName, true, trainingDataExpectedOutputLayer)
if (err != nil) { return false, nil, err }
predictedOutcomeName, err := geneticPrediction.GetOutcomeNameFromOutputLayer(traitName, true, predictionLayer)
predictedOutcomeName, err := geneticPrediction.GetDiscreteOutcomeNameFromOutputLayer(traitName, true, predictionLayer)
if (err != nil) { return false, nil, err }
getPredictionIsCorrectBool := func()bool{
@ -1565,7 +1578,7 @@ func setStartAndMonitorTestModelPage(window fyne.Window, traitName string, previ
// This map stores the accuracy for each outcome
traitPredictionAccuracyInfoMap := make(map[geneticPrediction.DiscreteTraitOutcomeInfo]geneticPrediction.DiscreteTraitPredictionAccuracyInfo)
for traitAccuracyData, value := range traitPredictionInfoMap{
for traitPredictionInfo, value := range traitPredictionInfoMap{
quantityOfExamples := value.QuantityOfExamples
quantityOfPredictions := value.QuantityOfPredictions
@ -1601,7 +1614,7 @@ func setStartAndMonitorTestModelPage(window fyne.Window, traitName string, previ
newTraitPredictionAccuracyInfo.ProbabilityOfCorrectOutcomePrediction = percentageOfCorrectOutcomePredictions
}
traitPredictionAccuracyInfoMap[traitAccuracyData] = newTraitPredictionAccuracyInfo
traitPredictionAccuracyInfoMap[traitPredictionInfo] = newTraitPredictionAccuracyInfo
}
// Testing is complete.
@ -1635,6 +1648,224 @@ func setStartAndMonitorTestModelPage(window fyne.Window, traitName string, previ
}
setViewModelTestingDiscreteTraitResultsPage(window, traitName, traitPredictionAccuracyInfoMap, previousPage)
} else {
// traitIsDiscreteOrNumeric == "Numeric"
//Outputs:
// -bool: Process completed (true == was not stopped mid-way)
// -geneticPrediction.NumericTraitPredictionAccuracyInfoMap
// -error
testModel := func()(bool, geneticPrediction.NumericTraitPredictionAccuracyInfoMap, error){
// 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)
_, testingSetFilepathsList, err := getTrainingAndTestingDataFilepathLists(traitName)
if (err != nil) { return false, nil, err }
traitNameWithoutWhitespaces := strings.ReplaceAll(traitName, " ", "")
// We read the trained model for this trait
modelFilename := traitNameWithoutWhitespaces + "Model.gob"
trainedModelFilepath := goFilepath.Join("./TrainedModels/", modelFilename)
fileExists, fileContents, err := localFilesystem.GetFileContents(trainedModelFilepath)
if (err != nil) { return false, nil, err }
if (fileExists == false){
return false, nil, errors.New("TrainedModel not found: " + trainedModelFilepath)
}
neuralNetworkObject, err := geneticPrediction.DecodeBytesToNeuralNetworkObject(fileContents)
if (err != nil) { return false, nil, err }
numberOfTrainingDatas := len(testingSetFilepathsList)
numberOfTrainingDatasString := helpers.ConvertIntToString(numberOfTrainingDatas)
finalIndex := numberOfTrainingDatas - 1
for index, filePath := range testingSetFilepathsList{
testModelIsStoppedBoolMutex.RLock()
testModelIsStopped := testModelIsStoppedBool
testModelIsStoppedBoolMutex.RUnlock()
if (testModelIsStopped == true){
// User exited the process
return false, nil, nil
}
fileExists, fileContents, err := localFilesystem.GetFileContents(filePath)
if (err != nil) { return false, nil, err }
if (fileExists == false){
return false, nil, errors.New("TrainingData file not found: " + filePath)
}
trainingDataObject, err := geneticPrediction.DecodeBytesToTrainingDataObject(fileContents)
if (err != nil) { return false, nil, err }
trainingDataInputLayer := trainingDataObject.InputLayer
trainingDataExpectedOutputLayer := trainingDataObject.OutputLayer
predictionLayer, err := geneticPrediction.GetNeuralNetworkRawPrediction(&neuralNetworkObject, false, 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 }
predictedOutcomeValue, err := geneticPrediction.GetNumericOutcomeValueFromOutputLayer(traitName, predictionLayer)
if (err != nil) { return false, nil, err }
numberOfKnownLoci, numberOfKnownAndPhasedLoci, numberOfLoci, err := geneticPrediction.GetLociInfoFromNetworkInputLayer(trainingDataInputLayer)
if (err != nil) { return false, nil, err }
proportionOfLociTested := float64(numberOfKnownLoci)/float64(numberOfLoci)
percentageOfLociTested := int(100*proportionOfLociTested)
proportionOfPhasedLoci := float64(numberOfKnownAndPhasedLoci)/float64(numberOfKnownLoci)
percentageOfPhasedLoci := int(100*proportionOfPhasedLoci)
newNumericTraitOutcomeInfo := geneticPrediction.NumericTraitOutcomeInfo{
OutcomeValue: predictedOutcomeValue,
PercentageOfLociTested: percentageOfLociTested,
PercentageOfPhasedLoci: percentageOfPhasedLoci,
}
existingList, exists := traitPredictionInfoMap[newNumericTraitOutcomeInfo]
if (exists == false){
traitPredictionInfoMap[newNumericTraitOutcomeInfo] = []float64{correctOutcomeValue}
} else {
existingList = append(existingList, correctOutcomeValue)
traitPredictionInfoMap[newNumericTraitOutcomeInfo] = existingList
}
exampleIndexString := helpers.ConvertIntToString(index+1)
numberOfExamplesProgress := "Tested " + exampleIndexString + "/" + numberOfTrainingDatasString + " Examples"
progressDetailsBinding.Set(numberOfExamplesProgress)
newProgressFloat64 := float64(index)/float64(finalIndex)
progressPercentageBinding.Set(newProgressFloat64)
}
// Now we construct the TraitAccuracyInfoMap
// This map stores the accuracy for each outcome
traitPredictionAccuracyInfoMap := make(map[geneticPrediction.NumericTraitOutcomeInfo]geneticPrediction.NumericTraitPredictionAccuracyRangesMap)
for traitPredictionInfo, realOutcomesList := range traitPredictionInfoMap{
if (len(realOutcomesList) == 0){
return false, nil, errors.New("traitPredictionInfoMap contains empty realOutcomesList.")
}
// 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)
}
}
quantityOfValuesInRange := len(valuesInRangeList)
totalQuantityOfValues := len(realOutcomesList)
proportionOfValuesInRange := float64(quantityOfValuesInRange)/float64(totalQuantityOfValues)
percentageOfValuesInRange := proportionOfValuesInRange * 100
if (percentageOfValuesInRange >= 1){
percentageOfValuesInRangeInt := int(percentageOfValuesInRange)
newNumericTraitPredictionAccuracyRangesMap[percentageOfValuesInRangeInt] = rangeDistance
}
if (quantityOfValuesInRange == totalQuantityOfValues){
newNumericTraitPredictionAccuracyRangesMap[100] = rangeDistance
break
}
// Now we increase rangeDistance
// We find the distance to the next closest item in the list that isn't already in our range
nearestValueDistance := float64(0)
for index, outcomeValue := range valuesOutOfRangeList{
distance := math.Abs(predictionValue - outcomeValue)
if (index == 0 || distance < nearestValueDistance){
nearestValueDistance = distance
}
}
rangeDistance += nearestValueDistance
}
traitPredictionAccuracyInfoMap[traitPredictionInfo] = newNumericTraitPredictionAccuracyRangesMap
}
// Testing is complete.
// We save the info map as a file in the ModelAccuracies folder
fileBytes, err := geneticPrediction.EncodeNumericTraitPredictionAccuracyInfoMapToBytes(traitPredictionAccuracyInfoMap)
if (err != nil) { return false, nil, err }
_, err = localFilesystem.CreateFolder("./ModelAccuracies")
if (err != nil) { return false, nil, err }
modelAccuracyFilename := traitNameWithoutWhitespaces + "ModelAccuracy.gob"
err = localFilesystem.CreateOrOverwriteFile(fileBytes, "./ModelAccuracies/", modelAccuracyFilename)
if (err != nil) { return false, nil, err }
progressPercentageBinding.Set(1)
return true, traitPredictionAccuracyInfoMap, nil
}
processIsComplete, traitPredictionAccuracyInfoMap, err := testModel()
if (err != nil){
setErrorEncounteredPage(window, err, previousPage)
return
}
if (processIsComplete == false){
// User exited the page
return
}
setViewModelTestingNumericTraitResultsPage(window, traitName, traitPredictionAccuracyInfoMap, previousPage)
}
}
@ -1680,6 +1911,11 @@ func setViewModelTestingDiscreteTraitResultsPage(window fyne.Window, traitName s
traitObject, err := traits.GetTraitObject(traitName)
if (err != nil) { return nil, err }
traitIsDiscreteOrNumeric := traitObject.DiscreteOrNumeric
if (traitIsDiscreteOrNumeric != "Discrete"){
return nil, errors.New("setViewModelTestingDiscreteTraitResultsPage called with non-discrete trait: " + traitName)
}
outcomeNamesList := traitObject.OutcomesList
for _, outcomeName := range outcomeNamesList{
@ -1801,6 +2037,175 @@ func setViewModelTestingDiscreteTraitResultsPage(window fyne.Window, traitName s
}
// This is a page to view the details of testing for a specific trait's model
func setViewModelTestingNumericTraitResultsPage(window fyne.Window, traitName string, traitAccuracyInfoMap geneticPrediction.NumericTraitPredictionAccuracyInfoMap, exitPage func()){
title := getBoldLabelCentered("Numeric Trait Prediction Accuracy Details")
exitButton := getWidgetCentered(widget.NewButtonWithIcon("Exit", theme.CancelIcon(), exitPage))
description1 := getLabelCentered("The results of the prediction accuracy for this trait are below.")
traitNameTitle := widget.NewLabel("Trait Name:")
traitNameLabel := getBoldLabel(traitName)
traitNameRow := container.NewHBox(layout.NewSpacer(), traitNameTitle, traitNameLabel, layout.NewSpacer())
description2 := getLabelCentered("Each value is a range that the prediction must be widened by to be accurate X% of the time.")
description3 := getLabelCentered("For example, for a height prediction to be accurate 90% of the time, allow a +/-10 cm range.")
getResultsGrid := func()(*fyne.Container, error){
probabilityOfTitle := getItalicLabelCentered("Probability Of")
correctPredictionTitle := getItalicLabelCentered("Correct Prediction")
accuracyRangeTitle1 := getItalicLabelCentered("Accuracy Range")
knownLociLabel_0to33 := getItalicLabelCentered("0-33% Known Loci")
accuracyRangeTitle2 := getItalicLabelCentered("Accuracy Range")
knownLociLabel_34to66 := getItalicLabelCentered("34-66% Known Loci")
accuracyRangeTitle3 := getItalicLabelCentered("Accuracy Range")
knownLociLabel_67to100 := getItalicLabelCentered("67-100% Known Loci")
probabilityOfCorrectPredictionColumn := container.NewVBox(probabilityOfTitle, correctPredictionTitle, widget.NewSeparator())
accuracyRangeColumn_0to33 := container.NewVBox(accuracyRangeTitle1, knownLociLabel_0to33, widget.NewSeparator())
accuracyRangeColumn_34to66 := container.NewVBox(accuracyRangeTitle2, knownLociLabel_34to66, widget.NewSeparator())
accuracyRangeColumn_67to100 := container.NewVBox(accuracyRangeTitle3, knownLociLabel_67to100, widget.NewSeparator())
traitObject, err := traits.GetTraitObject(traitName)
if (err != nil) { return nil, err }
traitIsDiscreteOrNumeric := traitObject.DiscreteOrNumeric
if (traitIsDiscreteOrNumeric != "Numeric"){
return nil, errors.New("setViewModelTestingNumericTraitResultsPage called with non-discrete trait: " + traitName)
}
probabilityMinimumRange := 1
for {
if (probabilityMinimumRange == 100){
break
}
getProbabilityMaximumRange := func()int{
if (probabilityMinimumRange == 90){
return 100
}
probabilityMaximumRange := probabilityMinimumRange + 9
return probabilityMaximumRange
}
probabilityMaximumRange := getProbabilityMaximumRange()
probabilityMinimumRangeString := helpers.ConvertIntToString(probabilityMinimumRange)
probabilityMaximumRangeString := helpers.ConvertIntToString(probabilityMaximumRange)
probabilityOfCorrectPredictionRangeFormatted := probabilityMinimumRangeString + "% - " + probabilityMaximumRangeString + "%"
probabilityOfCorrectPredictionRangeLabel := getBoldLabelCentered(probabilityOfCorrectPredictionRangeFormatted)
// We use the below variables to sum up the accuracy distances so we can average them
predictionAccuracyDistancesSum_0to33 := float64(0)
distancesCount_0to33 := 0
predictionAccuracyDistancesSum_34to66 := float64(0)
distancesCount_34to66 := 0
predictionAccuracyDistancesSum_67to100 := float64(0)
distancesCount_67to100 := 0
for traitOutcomeInfo, traitPredictionAccuracyRangesMap := range traitAccuracyInfoMap{
percentageOfLociTested := traitOutcomeInfo.PercentageOfLociTested
for percentageCorrect, distance := range traitPredictionAccuracyRangesMap{
if (percentageCorrect < probabilityMinimumRange || percentageCorrect > probabilityMaximumRange){
continue
}
if (percentageOfLociTested <= 33){
predictionAccuracyDistancesSum_0to33 += distance
distancesCount_0to33 += 1
} else if (percentageOfLociTested > 33 && percentageOfLociTested <= 66){
predictionAccuracyDistancesSum_34to66 += distance
distancesCount_34to66 += 1
} else {
predictionAccuracyDistancesSum_67to100 += distance
distancesCount_67to100 += 1
}
}
}
getAverageAccuracyText := func(distancesSum float64, distancesCount int)string{
if (distancesCount == 0){
return "Unknown"
}
averageDistance := distancesSum/float64(distancesCount)
averageDistanceString := helpers.ConvertFloat64ToStringRounded(averageDistance, 1)
//TODO: Retrieve units from traits package?
result := "+/- " + averageDistanceString + " centimeters"
return result
}
averageDistanceText_0to33 := getAverageAccuracyText(predictionAccuracyDistancesSum_0to33, distancesCount_0to33)
averageDistanceText_34to66 := getAverageAccuracyText(predictionAccuracyDistancesSum_34to66, distancesCount_34to66)
averageDistanceText_67to100 := getAverageAccuracyText(predictionAccuracyDistancesSum_67to100, distancesCount_67to100)
averageDistanceLabel_0to33 := getBoldLabelCentered(averageDistanceText_0to33)
averageDistanceLabel_34to66 := getBoldLabelCentered(averageDistanceText_34to66)
averageDistanceLabel_67to100 := getBoldLabelCentered(averageDistanceText_67to100)
probabilityOfCorrectPredictionColumn.Add(probabilityOfCorrectPredictionRangeLabel)
accuracyRangeColumn_0to33.Add(averageDistanceLabel_0to33)
accuracyRangeColumn_34to66.Add(averageDistanceLabel_34to66)
accuracyRangeColumn_67to100.Add(averageDistanceLabel_67to100)
probabilityOfCorrectPredictionColumn.Add(widget.NewSeparator())
accuracyRangeColumn_0to33.Add(widget.NewSeparator())
accuracyRangeColumn_34to66.Add(widget.NewSeparator())
accuracyRangeColumn_67to100.Add(widget.NewSeparator())
if (probabilityMinimumRange == 1){
probabilityMinimumRange = 10
} else {
probabilityMinimumRange += 10
}
}
resultsGrid := container.NewHBox(layout.NewSpacer(), probabilityOfCorrectPredictionColumn, accuracyRangeColumn_0to33, accuracyRangeColumn_34to66, accuracyRangeColumn_67to100, layout.NewSpacer())
return resultsGrid, nil
}
resultsGrid, err := getResultsGrid()
if (err != nil){
setErrorEncounteredPage(window, err, func(){setHomePage(window)})
return
}
page := container.NewVBox(title, exitButton, widget.NewSeparator(), description1, widget.NewSeparator(), traitNameRow, widget.NewSeparator(), description2, description3, widget.NewSeparator(), resultsGrid)
pageScrollable := container.NewVScroll(page)
window.SetContent(pageScrollable)
}
// This function returns a list of training data and testing data filepaths for a trait.
//Outputs:
// -[]string: Sorted list of training data filepaths
@ -1808,7 +2213,7 @@ func setViewModelTestingDiscreteTraitResultsPage(window fyne.Window, traitName s
// -error
func getTrainingAndTestingDataFilepathLists(traitName string)([]string, []string, error){
if (traitName != "Eye Color" && traitName != "Lactose Tolerance"){
if (traitName != "Eye Color" && traitName != "Lactose Tolerance" && traitName != "Height"){
return nil, nil, errors.New("getTrainingAndTestingDataFilepathLists called with invalid traitName: " + traitName)
}
@ -1845,11 +2250,14 @@ func getTrainingAndTestingDataFilepathLists(traitName string)([]string, []string
if (traitName == "Eye Color"){
return 112953, nil
return 113648, nil
} else if (traitName == "Lactose Tolerance"){
return 24808, nil
return 24872, nil
} else if (traitName == "Height"){
return 92281, nil
}
return 0, errors.New("Unknown traitName: " + traitName)
@ -1862,7 +2270,7 @@ func getTrainingAndTestingDataFilepathLists(traitName string)([]string, []string
numberOfTrainingDataFilesString := helpers.ConvertIntToString(numberOfTrainingDataFiles)
return nil, nil, errors.New(traitName + " number of training datas is unexpected: " + numberOfTrainingDataFilesString)
return nil, nil, errors.New(traitName + " quantity of training datas is unexpected: " + numberOfTrainingDataFilesString)
}
// We sort the training data to be in a deterministically random order