Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 151 additions & 0 deletions api/ws/handlers/expression-calculate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package wsHandler

import (
"context"
"errors"
"fmt"

"github.com/pixlise/core/v4/api/dbCollections"
"github.com/pixlise/core/v4/api/ws/wsHelpers"
"github.com/pixlise/core/v4/core/errorwithstatus"
protos "github.com/pixlise/core/v4/generated-protos"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"google.golang.org/protobuf/proto"
)

func HandleExpressionCalculateReq(req *protos.ExpressionCalculateReq, hctx wsHelpers.HandlerContext) (*protos.ExpressionCalculateResp, error) {
if len(req.Requests) <= 0 {
return nil, errorwithstatus.MakeBadRequestError(errors.New("Expected at least one request item"))
}

if hctx.SessUser.User.Id == "" {
return nil, errorwithstatus.MakeBadRequestError(errors.New("User must be logged in"))
}

resultItems := []*protos.RegionDataResultItem{}

for c, reqItem := range req.Requests {
if len(reqItem.ScanId) <= 0 {
return nil, errorwithstatus.MakeBadRequestError(fmt.Errorf("Request item %v must have a scan ID", c))
}
if len(reqItem.QuantId) <= 0 {
return nil, errorwithstatus.MakeBadRequestError(fmt.Errorf("Request item %v must have a quant ID", c))
}
if len(reqItem.ExpressionId) <= 0 {
return nil, errorwithstatus.MakeBadRequestError(fmt.Errorf("Request item %v must have an expression ID", c))
}
if len(reqItem.RoiId) <= 0 {
return nil, errorwithstatus.MakeBadRequestError(fmt.Errorf("Request item %v must have a region of interest ID", c))
}

// For now, we just find the memoised key of the latest expression version and looking it up. If it's not pre-computed we return an error
// Keys are of the form:
// {"scanId":"602735105","exprId":"q2ns80oc4452eldt","quantId":"quant-aqpxxfk6i05gcsy3","roiId":"AllPoints-602735105","units":0},Resp:false,exprMod:1772129285,spectra:3298,90,0
// So we need scan summary details and the expression last modified time
scanItem, _, err := wsHelpers.GetUserObjectById[protos.ScanItem](true, reqItem.ScanId, protos.ObjectType_OT_SCAN, dbCollections.ScansName, hctx)
if err != nil {
return nil, fmt.Errorf("Failed to read scan item for reqItem %v (%v): %v", c, reqItem.ScanId, err)
}

exprItem, _, err := wsHelpers.GetUserObjectById[protos.DataExpression](false, reqItem.ExpressionId, protos.ObjectType_OT_EXPRESSION, dbCollections.ExpressionsName, hctx)
if err != nil {
return nil, fmt.Errorf("Failed to read expression for reqItem %v (%v): %v", c, reqItem.ExpressionId, err)
}

normalSpectraCount := scanItem.ContentCounts["NormalSpectra"]
dwellSpectraCount := scanItem.ContentCounts["DwellSpectra"]

spectrumTimeStamp := 0 // Comes from SpectrumResp.timeStampUnixSec, seems to always be 0 for now??

memCacheKey := fmt.Sprintf(
`{"scanId":"%v","exprId":"%v","quantId":"%v","roiId":"%v","units":%v},Resp:false,exprMod:%v,spectra:%v,%v,%v`,
reqItem.ScanId,
reqItem.ExpressionId,
reqItem.QuantId,
reqItem.RoiId,
reqItem.Units.Number()-1,
exprItem.ModifiedUnixSec,
normalSpectraCount,
dwellSpectraCount,
spectrumTimeStamp,
)

// Read the item from memoisation cache
found := true
filter := bson.M{"_id": memCacheKey}
opts := options.FindOne()
result := hctx.Svcs.MongoDB.Collection(dbCollections.MemoisedItemsName).FindOne(context.TODO(), filter, opts)
if result.Err() != nil {
if result.Err() == mongo.ErrNoDocuments {
// Don't just quit here, we can return an individual error for this one item
found = false
//return nil, errorwithstatus.MakeNotFoundError(memCacheKey)
} else {
return nil, result.Err()
}
}

var resultItem *protos.RegionDataResultItem
if found {
// Decode the item
memItem := &protos.MemoisedItem{}
err = result.Decode(memItem)

if err != nil {
return nil, fmt.Errorf("Failed to read memoised item for reqItem %v (%v): %v", c, memCacheKey, err)
}

// Decode its embedded data
memResult, err := fromMemoised(memItem.Data)
if err != nil {
return nil, fmt.Errorf("Failed to read memoised data for reqItem %v (%v): %v", c, memCacheKey, err)
}

// Now form an item we can put in the response
resultItem = &protos.RegionDataResultItem{
ExprResult: memResult,
Expression: memResult.Expression,
// Error
// Warning
// RegionSettings
// Query
IsPMCTable: memResult.IsPMCTable,
}
} else {
// Send back an error for this item
resultItem = &protos.RegionDataResultItem{
// ExprResult
Expression: exprItem,
Error: "Failed to read cached expression result",
// Warning
// RegionSettings
// Query
IsPMCTable: false,
}
}

resultItems = append(resultItems, resultItem)
}

return &protos.ExpressionCalculateResp{
Result: &protos.RegionDataResults{
QueryResults: resultItems,
Error: "",
},
}, nil
}

// Written to match fromMemoised() in client code
func fromMemoised(data []byte) (*protos.MemDataQueryResult, error) {
memResult := &protos.MemDataQueryResult{}
err := proto.Unmarshal(data, memResult)

if err != nil {
return nil, err
}

// NOTE: we read the same MemDataQueryResult structure as we return
return memResult, nil
}
46 changes: 46 additions & 0 deletions core/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -1335,6 +1335,52 @@ func (c *APIClient) LoadMapData(key string) (*protos.ClientMap, error) {
return mapResult, nil
}

func (c *APIClient) CalculateExpression(scanId, quantId, expressionId, roiId string, units protos.DataUnit) (*protos.ClientMap, error) {
req := &protos.ExpressionCalculateReq{Requests: []*protos.DataSourceParams{{
ScanId: scanId,
QuantId: quantId,
ExpressionId: expressionId,
RoiId: roiId,
Units: units,
}}}

msg := &protos.WSMessage{Contents: &protos.WSMessage_ExpressionCalculateReq{
ExpressionCalculateReq: req,
}}

resps, err := c.sendMessageWaitResponse(msg)
if err != nil {
return nil, err
}

resp := resps[0].GetExpressionCalculateResp()

// Convert the result to a map
result := &protos.ClientMap{
EntryPMCs: []int32{},
FloatValues: []float64{},
}

if len(resp.Result.Error) > 0 {
return nil, fmt.Errorf("Error calculating expression: %v", resp.Result.Error)
}

if len(resp.Result.QueryResults) != 1 {
return nil, fmt.Errorf("Expected 1 expression calculation result, got %v", len(resp.Result.QueryResults))
}

if len(resp.Result.QueryResults[0].Error) > 0 {
return nil, fmt.Errorf("Error calculating expression (%v): %v", expressionId, resp.Result.Error)
}

for _, item := range resp.Result.QueryResults[0].ExprResult.ResultValues.Values {
result.EntryPMCs = append(result.EntryPMCs, int32(item.Pmc))
result.FloatValues = append(result.FloatValues, float64(item.Value))
}

return result, nil
}

func (c *APIClient) DeleteImage(imageName string) error {
req := &protos.ImageDeleteReq{Name: imageName}

Expand Down
9 changes: 8 additions & 1 deletion core/client/internal/cmdline/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,17 @@ func main() {
return
}

m, err := apiClient.CalculateExpression("669909507", "9b4h4zjuynpshf7c", "quant-tl2mrnxroian1acm", "AllPoints-669909507", 0)
if err != nil {
return
}
fmt.Printf("%+v\n", m)

k := "my-mapX"
//k = `{"scanId":"602735105","exprId":"h43yqjza1m23mmr0","quantId":"quant-aqpxxfk6i05gcsy3","roiId":"AllPoints-602735105","units":0},Resp:false,exprMod:1744785719,spectra:3298,90,0`
k = `{"scanId":"669909507","exprId":"9b4h4zjuynpshf7c","quantId":"quant-tl2mrnxroian1acm","roiId":"AllPoints-669909507","units":0},Resp:false,exprMod:1772072860,spectra:2754,6,0`

m, err := apiClient.LoadMapData(k)
m, err = apiClient.LoadMapData(k)
if err != nil {
return
}
Expand Down
7 changes: 7 additions & 0 deletions core/client/lib/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,13 @@ func loadMapData(key string) *C.char {
return processRequest("loadMapData", func() (proto.Message, error) { return apiClient.LoadMapData(key) })
}

//export calculateExpression
func calculateExpression(scanId, quantId, expressionId, roiId string, units int) *C.char {
return processRequest("calculateExpression", func() (proto.Message, error) {
return apiClient.CalculateExpression(scanId, quantId, expressionId, roiId, protos.DataUnit(units))
})
}

//export uploadImage
func uploadImage(imageUpload string) *C.char {
// Here we can read the data string as a protobuf message and create the right structure
Expand Down
Loading
Loading