发布新帖

検索

公告
· 五月 8, 2023

New Video: Navigating the Health Connect Cloud Portal

Hi All! Wanted to share a recent video we released, for those of you who may be getting started with InterSystems HealthShare Health Connect Cloud. This video walks through all the management tasks you can complete via the InterSystems Cloud Services Portal that you use with Health Connect Cloud. Feel free to reach out if you have questions!

讨论 (0)1
登录或注册以继续
文章
· 五月 8, 2023 阅读大约需 6 分钟

Facial recognition with Embedded Python and IRIS

Welcome, dear members of the community!

In this article we are going to demonstrate the great potential that IRIS/HealthConnect makes available to all its users with the use of Embedded Python and we are going to do it by developing a small production that will allow us to recognize and identify the faces present in a JPG file from some images that we will use as a reference.

Project configuration:

Let us begin! We have published the entire application in OpenExchange so that you only need to download and deploy it in Docker as you can read in the associated README file. As you will see, its name is Nomenclator, referring to the slaves who in Ancient Rome, during festivals and social events, stayed with their masters and were in charge of remembering the names of all those they met.

I am not an expert in Python so I have based the development on this article (in spanish) on the web, it is not necessary that we reinvent the wheel.

Let's take a look at the project sources, starting with the docker-compose.yml file:

version: "2.2"
services:
  # iris
  iris:
    init: true
    container_name: iris
    build:
      context: .
      dockerfile: iris/Dockerfile
    ports:
      - 52773:52773
      - 51773:51773
    command: --check-caps false
    volumes:
    - ./shared:/shared

As you can see it is extremely simple, we only need an instance of IRIS running. Let us explain above the most relevant files that are part of the project:

  • Dockerfile: file in charge of project configuration, it will install the necessary Python libraries in the container and deploy the code and production.
  • mobilenet_graph.pb: file with the pre-built model for the recognition of existing faces in JPG files.
  • facenet_keras_weights.h5: pre-built model for identifying faces from images stored in our repository
  • \shared: folders defined as volumes that will be accessible from Visual Studio Code and from the Docker container.
    • \JPG: folder where we will paste the image that we want to identify.
    • \knowns: folder that will function as a repository of our identified faces, our image will be compared with the ones we have in this folder, if there is a match we will show a message indicating the file with which it matches. For our example we have included a few photos of actor John Travolta.
    • \results: folder to store the result of the validations, an image will be included with the faces found within a box, this box will be green if the face matches one of those present in the \shared\knowns folder and red if it does not match.
    • \test: folder with 2 images that we will use for our tests, one of John Travolta (travi.jpg) and another of Nicolas Cage (nicolas.jpg).
    • \unknowns: folder to which we will move the image received in \shared\JPG for processing from the Python method.
  • Installer.cls: ObjectScript class that will create the NOMENCLATOR namespace and the associated database in our IRIS instance. 
  • Nomenclator.xml: xml file containing the production and business service code.
  • requeriments.txt: file with the Python libraries needed to run the project.

Previous warning:

This project is for educational purposes only and is not intended for direct use in any production environment.

Python code:

Let's briefly explain how our Python code works. Let's start with the code that is executed when we call the python method:

isMatch = ''
	# Embeddings reference
	known_embeddings = []
	for name in os.listdir(DIR_KNOWNS):
		if name.endswith('.jpg'):
			image = load_image(DIR_KNOWNS,name)
			bboxes = detect_faces(image)					
			face = extract_faces(image,bboxes)
			if len(face) > 0 :
				known_embeddings.append(compute_embedding(facenet,face[0]))				
					
	# Searching matches for knowns faces		
	for name in os.listdir(DIR_UNKNOWNS):
		if name.endswith('.jpg'):
			image = load_image(DIR_UNKNOWNS,name)
			bboxes = detect_faces(image)
			faces = extract_faces(image,bboxes)
      
			# Computing embedding for each face
			img_with_boxes = image.copy()
			for face, box in zip(faces,bboxes):
				emb = compute_embedding(facenet,face)

				puntuacion, reconocimiento = compare_faces(known_embeddings,emb)

				if any(reconocimiento):							
					isMatch = isMatch + ' ' + name + ' match!'
					img_with_boxes = draw_box(img_with_boxes,box,(0,255,0))
				else:
					img_with_boxes = draw_box(img_with_boxes,box,(255,0,0))
            
			cv2.imwrite(f'{DIR_RESULTS}/{name}',cv2.cvtColor(img_with_boxes,cv2.COLOR_RGB2BGR))
	
	return isMatch

Alright, let's get started, we have our isMatch variable into which we will load our method's response to the entered image. Next we have a for loop over all the files we have in the known images directory (what we have called our repository) in which we will first detect all the faces for the known images (detect_faces) using the pre-built model present in the mobilenet_graph.pb file, we will extract them from the file in the format we need (extract_faces) and we will extract their embeddings (compute_embedding) using the model defined by keras present in the facenet_keras_weights.h5 file, the embedding will not be more than a vector of eigenvalues of every face found in the known image files.

Once we have obtained the embeddings for all the known faces that we have, we will repeat the process with the unknown images that we want to validate, we will first detect the faces present in the images, we will extract them and finally we will calculate their embedding individually. With the calculated embedding we can compare it with the embeddings we obtained with the familiar faces.

Let's take a look at the Python function that performs the comparison:

def compare_faces(embs_ref, emb_desc, umbral=1.1):
		distancias = []
		for emb_ref in embs_ref:
			distancias.append(np.linalg.norm(emb_ref-emb_desc))
		distancias = np.array(distancias)
		return distancias, list(distancias<=umbral)

Here we see how we go through all the known embeddings that we have and compare it with the one belonging to the unknown image that we have, if the "distance" of both embeddings is less than 1.1 we will consider that they are the same person, otherwise we will not have a coincidence. The 1.1 value is something that you can fine-tune on your own by raising or lowering it to suit your needs.

Once the comparison process is finished, we will create a file that we will leave in the \shared\results folder with the original image and a green frame on the face if there is a match or red if there is not.

Demonstration:

Let's see an example of how it would all work together.

First let's copy the images we have in our \shared\test folder to \shared\JPG

Let's see what our production has done in IRIS:

Perfect! We have captured the two images from our /shared/JPG directory, we have processed them and we have obtained a match for the travi.jpg file and a divergence for the nicolas.jpg file. Let's look at the files in our /shared/results folder:

Here's good old Nicolas sad because he didn't get a match with our known faces. Let's see the other image.

Brilliant! John is more than happy to have been recognized for our IRIS production.

As I said before this is a simple example of the potential of IRIS with Embedded Python, you could improve this example by storing the embeddings of each known image in the IRIS database and we would save the step of calculating it every time we have a new image to test, since we would only have to load them from the database.

Well this would be all. I hope you find it useful and if you have any questions or suggestions do not hesitate to write a comment.

PS: a choco-point for those who have discovered the reference to the film Face off.

2 Comments
讨论 (2)1
登录或注册以继续
请注意,此帖子已过时。
公告
· 五月 2, 2023

[Video] Healthcare Action Engine and CDS Hooks: Sneak Peek

Hi Community,

Watch this video to learn how the upcoming Healthcare Action Engine will combine event detection, complex logic, and a notification framework to improve synchronous and asynchronous notifications:

⏯ Healthcare Action Engine and CDS Hooks: Sneak Peek @ Global Summit 2022

🗣  Presenter: @Frank Pandolfe, Clinical Product Specialist, InterSystems

Subscribe to InterSystems Developers YouTube to stay up to date!

讨论 (0)2
登录或注册以继续
问题
· 五月 1, 2023

Email Alert - EnsLib.EMail.OutboundAdapter Error

I am running into an error trying to send an Alert Email to test the functionality of IRIS HealthShare Health Connect  2022.1 compared to Cache HealthShare Health Connect 2018.1.3. I was trying to send an Alert email, when I am getting the following error on my EMailAlert operation which is using EnsLib.EMail.OutboundAdpater.

ERROR <Ens>ErrException: <UNDEFINED>FText+4 ^%occMessages *msg -- logged as '-'
number - @''

I verified the message to the EMailAlert was populated, so what could be throwing this error...

 

I found the problem to be with osuwmc.AlertEmail that was created to shorten the data within the email from EnsLib.Email.OutboundAdapter. Can someone take a look at the following code to see if it needs updated for some reason???


Class osuwmc.AlertEmail Extends Ens.Alerting.NotificationOperation [ ClassType = "", ProcedureBlock ]
{

Parameter ADAPTER = "EnsLib.EMail.OutboundAdapter";

Parameter SETTINGS = "IncludeDetails:Basic,SubjectPrefix:Basic,IncludeNodeinSubject:Basic,IncludeManagedAlertHistory:Basic";

Property Adapter As EnsLib.EMail.OutboundAdapter;

/// Control whether detailed information is added to emails sent for standard <class>Ens.AlertRequest</class>
/// messages. The default is 0 (or False) for backward compatibility reasons.
Property IncludeDetails As %Boolean [ InitialExpression = 0 ];

/// Control whether all Managed Alert actions will be included in the email, and the order in which the actions are included.
Property IncludeManagedAlertHistory As %Integer(DISPLAYLIST = ",False,Oldest First,Newest First", VALUELIST = ",0,1,2") [ InitialExpression = 0 ];

/// Prefix to include in the subject of emails sent by this system.
Property SubjectPrefix As %String;

/// This controls whether network information for the node is included in the subject of the outgoing email.
/// If you have many Ensemble installations on multiple systems, this may make it much
/// simpler to identify which system issued an alert.
Property IncludeNodeinSubject As %Boolean [ InitialExpression = 0 ];

/// Property to hold the basic text used to separate sub-sections of each email message.
Property SepLine As %String [ InitialExpression = "===============================================================" ];

Method OnMessage(pRequest As %Library.Persistent, Output pResponse As Ens.Response) As %Status
{
    If pRequest.%Extends("Ens.Alerting.NotificationRequest") {
        Quit ..OnAlertNotification(pRequest,.pResponse)
    }
    Quit ..OnAlertRequest(pRequest,.pResponse)
}

/// Main handler for requests of type <class>Ens.AlertRequest</class>.
/// If <property>IncludeDetails</property> is true, then the actual output will be
/// performed in <method>CreateEmailFromAlertRequest</class>.
Method OnAlertRequest(pAlertRequest As Ens.AlertRequest, Output pResponse As Ens.Response) As %Status
{
    If ..IncludeDetails {
        Set tSC = ..CreateEmailFromAlertRequest(pAlertRequest,.tMailMessage)
        If $$$ISERR(tSC) Quit tSC
    }
    Else {
        Set tMailMessage = ##class(%Net.MailMessage).%New()
        Set tMailMessage.Subject = "Ensemble alert from " _ ..InstanceName _ ":"_pAlertRequest.SourceConfigName
        Set tMailMessage.Charset = "iso-8859-1"
        If (pAlertRequest.AlertDestination '= "") {
            #; if the Ens.AlertRequest supplied an AlertDestination, then add it to the list of configured Recipients
            Set tSC=..Adapter.AddRecipients(tMailMessage, pAlertRequest.AlertDestination)  Quit:$$$ISERR(tSC) tSC
        }
        Set tSC = tMailMessage.TextData.Write(pAlertRequest.AlertText)
        Quit:$$$ISERR(tSC) tSC
    }
    Quit ..Adapter.SendMail(tMailMessage)
}

/// Create a notification email from a standard <class>Ens.AlertRequest</class> object.
Method CreateEmailFromAlertRequest(pAlertRequest As Ens.AlertRequest, Output pMailMessage As %Net.MailMessage) As %Status
{
    $$$LOGINFO("CreateEmailFromAlertRequest")
    Set tStatus = $$$OK
    Try {
        Set pMailMessage = ##class(%Net.MailMessage).%New()
        Set pMailMessage.Charset = "UTF-8"
        Set pMailMessage.Subject = ..SubjectPrefix _ $select(..SubjectPrefix '= "": " ", 1: "")

        Do ..GetSystemInfo(,.tSystemName,.tInstanceName,.tNodeName)

        If ..IncludeNodeinSubject {
            Set pMailMessage.Subject = pMailMessage.Subject _ $$$FormatText($$$Text("Ensemble Alert from configuration item '%1' in system '%2' on node '%3'","Ensemble"),pAlertRequest.SourceConfigName,tSystemName,tNodeName)
        }
        Else {
            Set pMailMessage.Subject = pMailMessage.Subject _ $$$FormatText($$$Text("Ensemble Alert from configuration item '%1' on system '%2'","Ensemble"),pAlertRequest.SourceConfigName,tSystemName)
        }

        If (pAlertRequest.AlertDestination '= "") {
            Set tStatus = ..Adapter.AddRecipients(pMailMessage, pAlertRequest.AlertDestination)
            If $$$ISERR(tStatus) Quit
        }
        Set tNow = $$$timeUTC
        Set tNowLocal = $$$timeUTCtoLocal(tNow)
        Set tTimeMessage = $select(..IncludeUTCTimes: $$$FormatText($$$Text("Ensemble alert email triggered at %1 [%2 UTC]","Ensemble"),tNowLocal,tNow), 1 : $$$FormatText($$$Text("Ensemble alert email triggered at %1","Ensemble"),tNowLocal))
        Set tStatus = pMailMessage.TextData.WriteLine(tTimeMessage)
        If $$$ISERR(tStatus) Quit
        Set tStatus = ..AddBasicData(pMailMessage,pAlertRequest.AlertText,pAlertRequest.AlertTime,pAlertRequest.SourceConfigName,$get($$$EnsRuntime("Name")),pAlertRequest.SessionId,tSystemName,tInstanceName)
        If $$$ISERR(tStatus) Quit
    }
    Catch {
        Set tStatus = $$$EnsSystemError
    }
    Quit tStatus
}

/// Send a notification email for the incoming <class>Ens.Alerting.NotificationRequest</class> in <var>pNotificationRequest</var>.
Method OnAlertNotification(pNotificationRequest As Ens.Alerting.NotificationRequest, Output pResponse As Ens.Response) As %Status
{
    $$$LOGINFO("OnAlertNotification")
    Set tStatus = $$$OK
    Try {
        Set tMailMessage = ##class(%Net.MailMessage).%New()
        Set tMailMessage.Charset = "UTF-8"
        Set tMailMessage.Subject = $select(..SubjectPrefix '= "": ..SubjectPrefix _ " ", 1: "")

        Set tManagedAlert = pNotificationRequest.ManagedAlert
        Do ..GetSystemInfo(tManagedAlert,.tSystemName,.tInstanceName,.tNodeName)
        
        If ..IncludeNodeinSubject {
            Set tMailMessage.Subject = tMailMessage.Subject _ $$$FormatText($$$Text("Ensemble ManagedAlert from configuration item '%1' in system '%2' on node '%3'","Ensemble"),tManagedAlert.SourceConfigName,tSystemName,tNodeName)
        }
        Else {
            Set tMailMessage.Subject = tMailMessage.Subject _ $$$FormatText($$$Text("Ensemble ManagedAlert from configuration item '%1' on system '%2'","Ensemble"),tManagedAlert.SourceConfigName,tSystemName)
        }
        If pNotificationRequest.NotificationType '= $$$eAlertNotificationRequest {
            Set tUpdateSubject = $case(pNotificationRequest.NotificationType, 
                $$$eAlertNotificationReminder: $$$Text("Reminder","Ensemble"),
                $$$eAlertNotificationEscalationChange: $$$Text("Escalation Change","Ensemble"),
                : "")
            If (tUpdateSubject '= "") {
                Set tMailMessage.Subject = tMailMessage.Subject _ " [" _ tUpdateSubject _ "]"
            }
            Set tStatus = ..GetUpdateText(pNotificationRequest,.tUpdateText)
            If $$$ISERR(tStatus) Quit
            //If ($get(tUpdateText) '= "") {
            //    Set tStatus = tMailMessage.TextData.WriteLine(tUpdateText)
            //    If $$$ISERR(tStatus) Quit
            //    Set tStatus = tMailMessage.TextData.WriteLine(..SepLine)
            //    If $$$ISERR(tStatus) Quit
            //}
        }

        For i=1:1:pNotificationRequest.AlertDestinations.Count() {
            Set tDestination = pNotificationRequest.AlertDestinations.GetAt(i)
            If (tDestination '= "") {
                Set tStatus = tMailMessage.To.Insert(tDestination)
                If $$$ISERR(tStatus) Quit
            }
        }
        If $$$ISERR(tStatus) Quit

        Set tStatus = ..AddBasicData(tMailMessage,tManagedAlert.AlertText,tManagedAlert.AlertTime,tManagedAlert.SourceConfigName,tManagedAlert.Production,tSystemName,tInstanceName)
        //If $$$ISERR(tStatus) Quit
        //Set tStatus = tMailMessage.TextData.WriteLine(..SepLine)
        //If $$$ISERR(tStatus) Quit
        //Set tStatus = tMailMessage.TextData.WriteLine($$$Text("Managed Alert Data","Ensemble"))
        //If $$$ISERR(tStatus) Quit
        //Set tStatus = tMailMessage.TextData.WriteLine(..SepLine)
        //If $$$ISERR(tStatus) Quit

        //Set tStatus = tMailMessage.TextData.WriteLine($$$Text("Managed Alert ID","Ensemble") _ ":" _ $char(9,9) _ tManagedAlert.%Id())
        //If $$$ISERR(tStatus) Quit
        //Set tStatus = tMailMessage.TextData.WriteLine($$$Text("Next Action Time","Ensemble") _ ":" _ $char(9,9) _ $$$timeUTCtoLocal(tManagedAlert.NextActionTime) _ $select(..IncludeUTCTimes: "  [" _ tManagedAlert.NextActionTime _ " UTC]", 1: ""))
        //If $$$ISERR(tStatus) Quit
        //Set tStatus = tMailMessage.TextData.WriteLine($$$Text("Escalation Level","Ensemble") _ ":" _ $char(9,9) _ tManagedAlert.EscalationLevel)
        //If $$$ISERR(tStatus) Quit
        //Set tStatus = tMailMessage.TextData.WriteLine($$$Text("Current Owner","Ensemble") _ ":" _ $char(9,9) _ $select(tManagedAlert.CurrentOwner = "": "[" _ $$$Text("No current owner","Ensemble") _ "]", 1: tManagedAlert.CurrentOwner))
        //If $$$ISERR(tStatus) Quit
        //Set tLastActionTime = tManagedAlert.LastActionTime
        //Set tStatus = tMailMessage.TextData.WriteLine($$$Text("Last Action Time","Ensemble") _ ":" _ $char(9,9) _ $$$timeUTCtoLocal(tLastActionTime) _ $select(..IncludeUTCTimes: "  [" _ tLastActionTime _ " UTC]", 1: ""))
        //If $$$ISERR(tStatus) Quit
        //Set tStatus = tMailMessage.TextData.WriteLine($$$Text("Next Monitor Time","Ensemble") _ ":" _ $char(9,9) _ $$$timeUTCtoLocal(tManagedAlert.NextMonitorTime) _ $select(..IncludeUTCTimes: "  [" _ tManagedAlert.NextMonitorTime _ " UTC]", 1: ""))
        //If $$$ISERR(tStatus) Quit
        
        //If ..IncludeManagedAlertHistory {
        //    Set tStatus = ..AddManagedAlertHistory(tMailMessage,tManagedAlert)
        //    If $$$ISERR(tStatus) Quit
        //}
        
        Set tStatus = ..Adapter.SendMail(tMailMessage)
        If $$$ISERR(tStatus) Quit
    }
    Catch {
        Set tStatus = $$$EnsSystemError
    }
    Quit tStatus
}

/// Helper method to write properties common to basic <class>Ens.AlertRequest</class> and
/// <class>Ens.Alerting.ManagedAlert</class> instances.
Method AddBasicData(pMailMessage As %Net.MailMessage, pAlertText As %String = "", pAlertTime As Ens.DataType.UTC = "", pSourceConfigName As %String = "", pProduction As %String = "", pSessionId As %Integer = "", pSystemName As %String = "", pInstanceName As %String = "") As %Status
{
    $$$LOGINFO("AddBasicData")
    Set tStatus = $$$OK
    Try {
        Set tStatus = pMailMessage.TextData.WriteLine(pAlertText)
        //$$$Text("Alert Text","Ensemble") _ ":" _ $char(9,9) _ 
        If $$$ISERR(tStatus) Quit
        //Set tStatus = pMailMessage.TextData.WriteLine($$$Text("Alert Time","Ensemble") _ ":" _ $char(9,9) _ $select(pAlertTime = "" : $$$Text("Unknown","Ensemble"), 1: $$$timeUTCtoLocal(pAlertTime) _ $select(..IncludeUTCTimes: "  [" _  pAlertTime _ " UTC]", 1: "")))
        //If $$$ISERR(tStatus) Quit
        //Set tStatus = pMailMessage.TextData.WriteLine($$$Text("Production","Ensemble") _ ":" _ $char(9,9) _ $select(pProduction = "": $$$Text("Unknown","Ensemble"), 1: pProduction))
        //If $$$ISERR(tStatus) Quit
        //Set tStatus = pMailMessage.TextData.WriteLine($$$Text("Source","Ensemble") _ ":" _ $char(9,9) _ pSourceConfigName)
        //If $$$ISERR(tStatus) Quit
        //If (pSessionId '= "") {
        //    Set tStatus = pMailMessage.TextData.WriteLine($$$Text("Session","Ensemble") _ ":" _ $char(9,9) _ pSessionId)
        //    If $$$ISERR(tStatus) Quit
        //}
        //If (pSystemName '= "") {
        //    Set tStatus = pMailMessage.TextData.WriteLine($$$Text("System","Ensemble") _ ":" _ $char(9,9) _ pSystemName)
        //    If $$$ISERR(tStatus) Quit
        //}
        //If (pInstanceName '= "") {
        //    Set tStatus = pMailMessage.TextData.WriteLine($$$Text("Instance","%Utility") _ ":" _ $char(9,9) _ pInstanceName)
        //    If $$$ISERR(tStatus) Quit
        //}
        
        //Set tStatus = pMailMessage.TextData.WriteLine($$$Text("Node","Ensemble") _ ":" _ $char(9,9) _ ##class(%SYS.System).GetNodeName())
        //If $$$ISERR(tStatus) Quit
    }
    Catch {
        Set tStatus = $$$EnsSystemError
    }
    Quit tStatus
}

/// Helper method to write out the Managed Alert History for a given ManagedAlert instance.
Method AddManagedAlertHistory(pMailMessage As %Net.MailMessage, pManagedAlert As Ens.Alerting.ManagedAlert) As %Status
{
    $$$LOGINFO("AddManagedAlertHistory")
    Set tStatus = $$$OK
    Try {
        Set tActionCount = pManagedAlert.Actions.Count()
        If (tActionCount > 0) {
            Set tStatus = pMailMessage.TextData.WriteLine(..SepLine)
            If $$$ISERR(tStatus) Quit
            Set tStatus = pMailMessage.TextData.WriteLine($$$Text("Managed Alert Action History","Ensemble"))
            If $$$ISERR(tStatus) Quit
            Set tStatus = pMailMessage.TextData.WriteLine(..SepLine)
            If $$$ISERR(tStatus) Quit
            If ..IncludeManagedAlertHistory = 1 {
                Set tStart = tActionCount
                Set tIncrement = -1
                Set tEnd = 1
            }
            Else {
                Set tStart = 1
                Set tIncrement = 1
                Set tEnd = tActionCount
            }
            For i=tStart:tIncrement:tEnd {
                Set tAction = pManagedAlert.Actions.GetAt(i)
                If $IsObject(tAction) {
                    Set tStatus = pMailMessage.TextData.WriteLine("(" _ i _ ")" _ $char(9,9) _ $$$Text("Action Time","Ensemble") _ ": " _ $$$timeUTCtoLocal(tAction.ActionTime) _ $select(..IncludeUTCTimes: " [" _ tAction.ActionTime _ " UTC]", 1: ""))
                    If $$$ISERR(tStatus) Quit
                    Set tStatus = pMailMessage.TextData.WriteLine($char(9,9) _ $$$Text("User","Ensemble") _ ": " _ tAction.Username)
                    If $$$ISERR(tStatus) Quit
                    Set tStatus = pMailMessage.TextData.WriteLine($char(9,9) _ $$$Text("Reason","Ensemble") _ ": " _ tAction.Reason)
                    If $$$ISERR(tStatus) Quit
                    #; Only write out changes if there are any
                    If (tAction.OldValues.Next("") '= "") {
                        Set tStatus = pMailMessage.TextData.WriteLine($char(9,9) _ $$$Text("Changed Values","Ensemble") _ ":")
                        If $$$ISERR(tStatus) Quit
                        Set tPropName = ""
                        For {
                            Set tOldValue = tAction.OldValues.GetNext(.tPropName)
                            If (tPropName = "") Quit
                            Set tNewValue = tAction.NewValues.GetAt(tPropName)
                            Set tStatus = pMailMessage.TextData.WriteLine($char(9,9,9) _ tPropName _": '" _ tOldValue _ "' => '" _ tNewValue _ "'")
                            If $$$ISERR(tStatus) Quit
                        }
                        If $$$ISERR(tStatus) Quit
                    }
                    Set tStatus = pMailMessage.TextData.WriteLine()
                    If $$$ISERR(tStatus) Quit
                }
            }
        }
    }
    Catch {
        Set tStatus = $$$EnsSystemError
    }
    Quit tStatus
}

}

5 Comments
讨论 (5)3
登录或注册以继续
文章
· 四月 28, 2023 阅读大约需 2 分钟

How to create a new idea on InterSystems Ideas

Hey Community!

Here is a short article on how to create an idea on InterSystems Ideas

0. Register on Ideas Portal if you aren't a member yet or log in. You can easily register using your InterSystems Developer Community ID.

1. Read carefully Portal Guide page on the Ideas Portal, especially "Idea promotion rules" section. All posted ideas are moderated following these rules.

2. Click on the "Add a new idea" button

and you will see the form to add the idea.

3. First, provide a one-sentence summary of the idea which is the required field. When you start typing, you will see a list of ideas with similar words in their names or tags. In case a similar idea is already created, vote or comment on this idea. The optimal size of an idea summary is 4-12 words.

4. Next, describe the idea in the "Please add more details" field.

In addition to text, you can attach screenshots or other files and insert tables and links. There is a full-screen mode that helps you see the whole description of your idea without scrolling.

5. Then you need to fill in the required field "Category". The correct category will help to assign your idea to the appropriate expert in the InterSystems team. 

In case you first sorted ideas by category and then pushed the button "Add a new idea", the idea's category will be added automatically.

6. Optionally, you can add tags to your idea, so other users can find it easily based on tags. The list of tags starts with tags having an "InterSystems" title, all other tags are sorted in alphabetical order.

7. Click on "Add idea" to submit.

    Hope this helps you share your ideas with others! If you have any questions, please send a direct message to @Vadim Aniskin.

    ---------------------

    * Please take into account that ideas and comments should be in English.
    * Ideas Portal admins can ask questions using Developer Community direct messages to clarify the idea and its category. Please answer these questions to make your idea visible to all users.
    * When you create an idea you automatically subscribe to e-mail notifications related to your idea, including:

    • changes in the status of your idea
    • comments on your idea posted by portal admins (you don't get notifications about comments from other users) 
    讨论 (0)1
    登录或注册以继续