Author: myrian studio

sampleprotocolsAvec la 2.3 il est désormais possible de paramétrer les protocoles à partir des préférences, en utilisant des formules XML.

Pour ce faire, on doit utiliser des “alias”. Je rappelle le principe: on définit une table de variables appelée “aliasmap” au début d’un Clinical Element, puis, dans les attributs XML, on référence ces variables par leur nom, en préfixant avec le signe $.

  <aliasmap uid="sampvolumestatus" viewlimit="4"/>
...
  <view appindex="0" limit="$viewlimit"/>

Dorénavant, on peut aussi indiquer que l’alias déterminera sa valeur en évaluant une formule XML. Evidemment, comme les alias sont évalués au chargement de l’objet qui contient l’aliasmap, tous les évaluateurs ne sont pas utilisables (parce que certains objets sont chargés au lancement de l’application par exemple et non à l’ouverture du workspace).

Dans l’exemple suivant, on utilise la fonction “app” pour lire une hypothétique préférence standard de Myrian qui s’appellerait “viewlimit”.

<?xml version="1.0" encoding="utf-16"?>
<protocol uid="sampvolumestatus" thumbnail="volumestatus.png" version="1500" >
  <aliasmap uid="sampvolumestatus" viewlimit="#eval:app(prefs,viewlimit)"/>
  <appset>
    <app refuid="appvolumestatus" />
  </appset>
  <mmconfig>
    <monitor>
      <pagelayout type="autogrid" />
      <view appindex="0" limit="$viewlimit"/>
    </monitor>
  </mmconfig>
</protocol>

On pourrait aussi aller lire une préférence quelconque dans _intrasense.txt:

<aliasmap uid="sampvolumestatus" viewlimit="#eval:app(prefs,sys:mysection:myentry:4)"/>

 

apptk1On peut dorénavant avoir des variables XML locales à un button group ou à un scénario. Il suffit de préfixer le nom de la variable partout où on l’utilise avec “local:”. Exemple:

<control type="text" title="Run Commands (2 series)" width="100%">
      <cmd id="WksVariableCmd" cmd="set" name="local:lossyarrived" content="0"/>
</control>

Myrian s’occupe en interne de remplace “local:” par le pointeur de l’objet parent (button group ou scénario).

L’event “varchanged” contiendra le nom d’origine (“local:xxxx”) et non le nom interne. A priori, seul l’objet parent est intéressé par un tel event, donc ça devrait suffire.

Intrasense and all the Myrian Studio team wish you the best for this new year, may 2017 bring you hapiness and good health.

We look forward to increase further interest for Myrian Studio and to work together on successful and challenging medical applications with all of you !

cartevoeux3

Il est possible d’écrire du code formaté en utilisant le plugin “Enlighter Code Insert”.

enlightercodeinsert

 

Lorsqu’on clique sur l’icone “Enlighter Code Insert”, un pop-up apparaît, dans lequel on peut définir le code à afficher.

enlightercodepopup

Cela donne le résultat suivant:

class WksSetSynchroData : public CommandDataPureXml {
public:
    WksSetSynchroData(const char **fields) : CommandDataPureXml(fields) {} 
};

 

On peut aussi se servir du même plugin pour afficher de l’Xml. Dans ce dernier cas il faut néanmoins sélectionner Xml comme langage.

<?xml version="1.0" encoding="utf-16"?>
<buttongroup uid="Liver-layouts120" title="##256">
    
  <layoutset>
  
    <layout >    
        <!-- Set navigation tool parameter to slow once for all in this protocol -->
        <trigger type="globalonce">
            <cmd id="ViewSendToolParameter" tooluid="navigate" setuid="navigate_slow"/>
        </trigger>
        <!-- List of Layout control -->
        <control uid="vps" type="vplayoutselector" width="100%">
            <param uiselect="icontext" useico="yes" minlayout="no" uids="LiverMRI120_1,LiverMRI120_2,LiverMRI120_3" />
        </control>
    </layout>    
                
  </layoutset>
        
  <!--Resource tables-->
<resources import="Liver-Resources"/>

</buttongroup>

 

logo_extended6Par défaut Myrian détecte automatiquement un nombre maximum de threads à utiliser pour chaque processus (en fonction du nombre de cœurs sur un nœud NUMA, ce qui correspond au nombre de cœurs sur un socket la plupart du temps). Dans certains cas, cette détection automatique fonctionne mal (du fait de la couche de virtualisation). Une préférence permet de passer outre cette détection automatique à partir de la 2.1.2 (MAX_THREAD_COUNT dans le [MAIN] de _intrasense.txt). Il est possible de connaître le nombre de threads effectivement utilisés par Myrian en ouvrant le log <user>.status.txt. Le nombre de threads est donné par la ligne « omp_set_num_threads=… ».

 

Mesures effectuées avec la 2.2:

Lorsque le nombre de threads à utiliser dans Myrian n’est pas défini correctement de manière automatique, le meilleur choix est 6 ou 8 en fonction de l’utilisation principale de Myrian.

Lorsqu’on mesure les performances en fonction du nombre de threads, on obtient de bonnes parallélisation jusqu’à 6 ou 8 threads :

  • Jusqu’à 6 threads, la parallélisation est correcte presque tout le temps.
  • Avec 8 threads, on a le même comportement la plupart du temps. Il y a quelques exceptions, les méthodes d’affichage 2d en particulier. De plus on aperçoit souvent une grosse augmentation du temps de calcul dans le pire des cas, ce qui pourrait induire des saccades (à l’affichage entre autres).
  • Au-dessus de 8 threads, il vaut souvent mieux réserver les ressources pour d’autres taches.

Du coup, en simplifiant un peu, il vaut mieux utiliser au plus :

  • 6 threads pour une activité principale de visualisation
  • 8 threads lorsqu’on compte faire beaucoup de segmentation, recalage et rendu 3D…

Bien sûr, cela dépend aussi des ressources disponibles sur le serveur, du nombre d’utilisateurs simultanés…

 

 

PS : Il s’agit des résultats des tests sur une VM précise. Je ne sais pas si ces résultats sont valides quelle que soit l’architecture matérielle et logicielle. En particulier, ces résultats ne seront plus valides si on a moins de 6/8 cœurs sur chaque nœud NUMA. Dans les autres cas, je ne vois pas pourquoi ces résultats ne seraient pas valides.

PS2 : Si dans _intrasense.txt, on choisit un nombre de CPU (dans MAX_THREAD_COUNT) supérieur au nombre de CPU logiques disponibles sur la machine, Myrian crashe.

PS3 : (Pour EQD) Lorsqu’on utilise un schedule dynamic (car plus efficace que le schedule static dans certains cas), on observe la plupart du temps une réduction importante des gains dus à la parallélisation lorsqu’on approche, puis dépasse le nombre de threads disponible sur un processeur physique (entre 8 et 16 threads sur la machine de test). Ce comportement s’explique par l’overhead de synchronisation entre les threads. Celui-ci devient bien plus important quand on travaille sur plusieurs processeurs physiques en même temps. Dans ces cas-là, l’utilisation d’un schedule static est parfois plus efficace lorsqu’on travaille sur un grand nombre de cœurs. Mais à mon avis il vaut mieux favoriser les gains de vitesse pour un nombre de threads plus faible.

synchrodlgNouvelle commande XML : WksSetSynchro

 

Comme son nom l’indique, cette commande permet de modifier la synchronisation entre vue et entre viewports, comme on le fait avec l’élément <synchro> des protocoles. C’est exactement la même syntaxe.

 

Un sample button group “sample-commands-synchro.buttongroup” fait la démo de la commande. Le plus simple est d’ouvrir 2 vues ainsi que la boite de dialogue utilisée habituellement pour régler la synchro. On peut voir les checkbox se mettre à jour au fur et à mesure des commandes qu’on envoie depuis le button group.

 

J’en profite au passage pour vous rappeler (ou vous apprendre) qu’on peut faire des commandes avec des listes d’attributs dynamiques, comme par exemple WksCustom ou WksSetSynchro. Il suffit de faire dériver les données de la commande de la classe CommandDataPureXml:

class WksSetSynchroData : public CommandDataPureXml {
public:
   WksSetSynchroData(const char **fields) : CommandDataPureXml(fields) {}
};

Ensuite dans la commande, on récupère les attributs avec GetFields:

bool CHepatoDlg::_ExecuteWksSetSynchro(constWksSetSynchroData *data)
{
   BasicXmlObject synchro(XML_SYNCHRO);
   // convert fields to XML object
   const std::vector<std::string>& fields = data->GetFields();
   for (auto f = fields.begin(); f != fields.end(); f++)
   {
      CString name = f->c_str();
      f++;
      synchro.AddAtt(name, f->c_str());
   }

   // process object
   APP->GetProtocoles().InitSynchro(&synchro, false);

   return true;
}

 

logo-papillon61num_threads est un mot clef permettant de limiter le nombre de threads à utiliser dans une boucle parallélisée avec openmp.

Il faut faire attention avant de l’utiliser car il induit parfois un overhead important, même si on veut utiliser moins de threads que le nombre maximum de threads openmp (défini par omp_set_num_threads). Le comportement décrit par la suite n’est pas tout le temps rencontré. Je n’ai pas vraiment d’explication sur les facteurs qui le déclenchent.

 

Exemple

// A l’initialisation de Myrian
omp_set_num_threads(N);

OPENMP_CHRONO_START_ID(NoMask0);
#pragma omp parallel for
for (int32_t j = 0; j<sliceSize; ++j)
{
}
OPENMP_CHRONO_END_FOR_ID(NoMask0, sliceSize);

OPENMP_CHRONO_START_ID(NoMask1);
#pragma omp parallel for num_threads(6)
for (int32_t j = 0; j<sliceSize; ++j)
{
}
OPENMP_CHRONO_END_FOR_ID(NoMask1, sliceSize);

OPENMP_CHRONO_START_ID(NoMask2);
#pragma omp parallel for
for (int32_t j = 0; j<sliceSize; ++j)
{
}
OPENMP_CHRONO_END_FOR_ID(NoMask2, sliceSize);

 

Thread max (Myrian) Computation time (s)
NoMask0 NoMask1 NoMask2
6 0.001162 0.001045 0.000956
16 0.003494 2.27518 1.967162

 

L’overhead dû à la modification locale du nombre de threads est supérieur à 2ms. De plus on a à nouveau un overhead à la boucle openmp suivante qui utilise un nombre de threads différent de 6. Du coup, il faut que le code parallélisé soit assez long pour que cela vaille le coup.

 

Explication

Lorsqu’on définit le nombre de threads max pour openmp, un pool de threads est créé (afin d’éviter de perdre du temps à créer des threads à chaque parallélisation). Malheureusement, lorsqu’on demande à utiliser moins de threads que cette valeur max, il semblerait qu’on détruit ce pool, puis en recréé un nouveau avec un nombre de threads inférieur (alors qu’il suffirait d’utiliser uniquement une partie du pool). Du coup, chaque modification du nombre de thread a un impact non négligeable sur les temps de calcul.

 

PS : On a exactement le même comportement si on utilise omp_set_num_threads.

PS2 : Les macros OPENMP_CHRONO_ servent juste à mesurer le temps de calcul.

dicomvalidateIf you install David Clunie’s Dicom Tools, you will get a tool called Dicom Validator (dciodvfy.exe) that can parse a DICOM file and report invalid fields (either warnings or errors). You can fin that validator here: http://www.dclunie.com/dicom3tools/workinprogress/winexe/

You can now use this tool from within Myrian, from the contextual menu (right click) of the DICOM dump window that appears in various places (study list thumbnails, scrapbook, viewports, import, etc). All you have to do is declare the location of tool Inside _intrasense.txt

 

 [DEBUGGING]
 DICOMVALIDATOR=C:\Users\jca\Desktop\Dicom Validator\dciodvfy.exe

 

The path to the DICOM file will be added on the command line of the tool. If you need other command line options or want to use another tool, you can define a more complex command line with another setting. For example:

 [DEBUGGING]
 DICOMVALIDATOROPT=-i -verbose %file%

If you use that setting, you must explicit specify where to put the path to the DICOM file, using the tag %file%

Now when you start the tool from a DICOM Dump window, a DOS box will open and the tool will be executed on the DICOM file you are looking at. The results will appear directly in the DOS window.