1 /*
2  *
3  *  Copyright (C) 2015-2019, Open Connections GmbH
4  *  All rights reserved.  See COPYRIGHT file for details.
5  *
6  *  This software and supporting documentation are maintained by
7  *
8  *    OFFIS e.V.
9  *    R&D Division Health
10  *    Escherweg 2
11  *    D-26121 Oldenburg, Germany
12  *
13  *
14  *  Module:  dcmfg
15  *
16  *  Author:  Michael Onken
17  *
18  *  Purpose: Main interface class for managing Functional Groups
19  *
20  */
21 
22 #include "dcmtk/config/osconfig.h"
23 
24 #include "dcmtk/dcmfg/fg.h"
25 #include "dcmtk/dcmfg/fgfact.h" // for creating new functional groups
26 #include "dcmtk/dcmfg/fginterface.h"
27 #include "dcmtk/dcmiod/iodutil.h" // for static helpers
28 #include "dcmtk/ofstd/ofmap.h"
29 #include "dcmtk/ofstd/ofmem.h"
30 
FGInterface()31 FGInterface::FGInterface()
32     : m_shared()
33     , m_perFrame()
34     , m_checkOnWrite(OFTrue)
35 {
36 }
37 
~FGInterface()38 FGInterface::~FGInterface()
39 {
40     clear();
41 }
42 
clear()43 void FGInterface::clear()
44 {
45     // Clear per frame functional groups
46     while (m_perFrame.size() > 0)
47     {
48         OFMap<Uint32, FunctionalGroups*>::iterator it = m_perFrame.begin();
49         FunctionalGroups* fg                          = (*it).second;
50         m_perFrame.erase(it);
51         delete fg;
52     }
53 
54     // Clear shared functional groups
55     m_shared.clear();
56 }
57 
getNumberOfFrames()58 size_t FGInterface::getNumberOfFrames()
59 {
60     return m_perFrame.size();
61 }
62 
addShared(const FGBase & group)63 OFCondition FGInterface::addShared(const FGBase& group)
64 {
65     DcmFGTypes::E_FGSharedType sharedType = group.getSharedType();
66     if (sharedType == DcmFGTypes::EFGS_ONLYPERFRAME)
67     {
68         DCMFG_ERROR("Cannot add group as shared, per DICOM, group type " << DcmFGTypes::FGType2OFString(group.getType())
69                                                                          << " is always per-frame");
70         return FG_EC_CouldNotAddFG;
71     }
72 
73     // Delete all per frame groups of this type
74     for (size_t count = 0; count < m_perFrame.size(); count++)
75     {
76         deletePerFrame(OFstatic_cast(Uint32, count), group.getType());
77     }
78 
79     // Create copy for insertion
80     FGBase* copy = group.clone();
81     if (!copy)
82     {
83         return EC_MemoryExhausted;
84     }
85 
86     // Insert shared one, replace old one if existing
87     OFCondition result = insertShared(copy, OFTrue /* replace */);
88     if (result.bad())
89     {
90         DCMFG_ERROR("Could not add shared group of type: " << DcmFGTypes::FGType2OFString(group.getType()));
91         delete copy;
92     }
93 
94     return result;
95 }
96 
addPerFrame(const Uint32 frameNo,const FGBase & group)97 OFCondition FGInterface::addPerFrame(const Uint32 frameNo, const FGBase& group)
98 {
99     OFCondition result                    = EC_Normal;
100     DcmFGTypes::E_FGSharedType sharedType = group.getSharedType();
101     if (sharedType == DcmFGTypes::EFGS_ONLYSHARED)
102     {
103         DCMFG_ERROR("Cannot add group as per-frame, group type " << DcmFGTypes::FGType2OFString(group.getType())
104                                                                  << " is always shared");
105         return FG_EC_CouldNotAddFG;
106     }
107 
108     // Check whether there is already a shared group of this type.
109     // If the content is equal to the given group, we re-use the shared one
110     FGBase* shared = getShared(group.getType());
111     // If there is a shared group
112     if (shared)
113     {
114         // If shared has identical values as given group, nothing has to be done.
115         // Else if shared group with such type exists, but content differs,
116         // we must the make the existing shared FG "per-frame", i.e. distribute
117         // it to all frames, and add the given group for the given frame.
118         if ((*shared).compare(group) != 0)
119         {
120             // We need to unshare this group, i.e. distribute it to frames
121             DCMFG_DEBUG("Converting shared group of type " << DcmFGTypes::FGType2OFString(
122                             group.getType()) << " to per-frame, triggered by deviating per-frame insertion");
123             result = convertSharedToPerFrame(group.getType());
124         }
125         else
126         {
127             DCMFG_DEBUG("Re-using shared group instead of adding per-frame for frame "
128                         << frameNo << ", type " << DcmFGTypes::FGType2OFString(group.getType()));
129             return EC_Normal;
130         }
131     }
132 
133     if (result.good())
134     {
135         FGBase* copy = group.clone();
136         if (!copy)
137         {
138             return EC_MemoryExhausted;
139         }
140         result = insertPerFrame(frameNo, copy);
141         if (result.bad())
142             delete copy;
143     }
144 
145     return result;
146 }
147 
148 // Get specific functional group for a frame,
149 // no matter whether it is stored per frame or shared
get(const Uint32 frameNo,const DcmFGTypes::E_FGType fgType)150 FGBase* FGInterface::get(const Uint32 frameNo, const DcmFGTypes::E_FGType fgType)
151 {
152     OFBool helpShared; // throw-away variable
153     return get(frameNo, fgType, helpShared);
154 }
155 
getPerFrame(const Uint32 frameNo) const156 const FunctionalGroups* FGInterface::getPerFrame(const Uint32 frameNo) const
157 {
158     if (frameNo > m_perFrame.size())
159     {
160         return NULL;
161     }
162     else
163     {
164         return (*(m_perFrame.find(frameNo))).second;
165     }
166 }
167 
getShared() const168 const FunctionalGroups* FGInterface::getShared() const
169 {
170     return &m_shared;
171 }
172 
173 // Read enhanced multi-frame information from DICOM item, usually DcmDataset
read(DcmItem & dataset)174 OFCondition FGInterface::read(DcmItem& dataset)
175 {
176     OFCondition result = EC_Normal;
177 
178     // clear any old values
179     clear();
180 
181     /* read shared functional groups */
182     if (result.good())
183     {
184         result = readSharedFG(dataset);
185     }
186 
187     /* read per frame functional groups */
188     if (result.good())
189     {
190         result = readPerFrameFG(dataset);
191     }
192 
193     return result;
194 }
195 
readSharedFG(DcmItem & dataset)196 OFCondition FGInterface::readSharedFG(DcmItem& dataset)
197 {
198     /* read shared functional groups */
199     DcmSequenceOfItems* shared = NULL;
200     OFCondition result         = dataset.findAndGetSequence(DCM_SharedFunctionalGroupsSequence, shared);
201     if (result.bad())
202     {
203         DCMFG_ERROR("Could not find Shared Functional Group Sequence");
204         return FG_EC_NoSharedFG;
205     }
206 
207     if (shared->card() > 1)
208     {
209         DCMFG_WARN("More than one item in Shared Functional Group Sequence, only considering the first one");
210     }
211     else if (shared->card() == 0)
212     {
213         DCMFG_WARN("No Item in Shared Functional Group Sequence but exactly one expected");
214         return FG_EC_NoSharedFG;
215     }
216 
217     // get the only item of shared functional group sequence
218     DcmItem* sharedFGs = shared->getItem(0);
219     // read all functional groups from shared fg sequence item
220     result = readSingleFG(*sharedFGs, m_shared);
221 
222     return result;
223 }
224 
readPerFrameFG(DcmItem & dataset)225 OFCondition FGInterface::readPerFrameFG(DcmItem& dataset)
226 {
227     /* read per-frame functional groups */
228     DcmSequenceOfItems* perFrame = NULL;
229     OFCondition result           = dataset.findAndGetSequence(DCM_PerFrameFunctionalGroupsSequence, perFrame);
230     if (result.bad())
231     {
232         DCMFG_ERROR("Could not find Per-Frame Functional Group Sequence");
233         return FG_EC_NoPerFrameFG;
234     }
235 
236     /* 1-n items required */
237     size_t numFrames = perFrame->card();
238     if (numFrames == 0)
239     {
240         DCMFG_WARN("No Item in Shared Functional Group Sequence but exactly one or more expected");
241         return FG_EC_NoPerFrameFG;
242     }
243 
244     /* Read functional groups for each item (one per frame) */
245     DcmItem* oneFrameItem = OFstatic_cast(DcmItem*, perFrame->nextInContainer(NULL));
246     Uint32 count          = 0;
247     while (oneFrameItem != NULL)
248     {
249         OFunique_ptr<FunctionalGroups> perFrameGroups(new FunctionalGroups());
250         if (!oneFrameItem)
251         {
252             DCMFG_ERROR("Could not get functional group item for frame #" << count << " (internal error)");
253         }
254         else if (!perFrameGroups.get())
255         {
256             DCMFG_ERROR("Could not create functional groups for frame #" << count << ": Memory exhausted?");
257         }
258         else
259         {
260             result = readSingleFG(*oneFrameItem, *perFrameGroups);
261             if (result.good())
262             {
263                 if (!m_perFrame.insert(OFMake_pair(count, perFrameGroups.release())).second)
264                 {
265                     DCMFG_ERROR("Could not store functional groups for frame #" << count << " (internal error)");
266                 }
267             }
268             else
269             {
270                 DCMFG_ERROR("Could not read functional groups for frame #" << count << ": " << result.text());
271             }
272         }
273         oneFrameItem = OFstatic_cast(DcmItem*, perFrame->nextInContainer(oneFrameItem));
274         count++;
275     }
276     return EC_Normal; // for now we always return EC_Normal...
277 }
278 
readSingleFG(DcmItem & fgItem,FunctionalGroups & groups)279 OFCondition FGInterface::readSingleFG(DcmItem& fgItem, FunctionalGroups& groups)
280 {
281     OFCondition result;
282     size_t card = fgItem.card();
283     OFString fgname;
284     for (size_t count = 0; count < card; count++)
285     {
286         DcmElement* elem = fgItem.getElement(OFstatic_cast(unsigned long, count));
287         // TODO: non-sequence elements are not explicitly forbidden here(?), we could store them and re-store them later
288         if (elem->getVR() != EVR_SQ)
289         {
290             DCMFG_WARN("Found non-sequence element in functional group sequence item (ignored): " << elem->getTag());
291         }
292         else
293         {
294             FGBase* fg = FGFactory::instance().create(elem->getTag());
295             if (fg != NULL)
296             {
297                 OFStringStream stream;
298                 stream << DcmFGTypes::tagKey2FGString(elem->getTag()) << " " << elem->getTag();
299                 OFSTRINGSTREAM_GETSTR(stream, tmpstr)
300                 fgname = tmpstr;
301                 OFSTRINGSTREAM_FREESTR(tmpstr)
302                 result = fg->read(fgItem);
303                 if (result.bad())
304                 {
305                     DCMFG_WARN("Cannot read functional group: " << fgname << " (ignored)");
306                 }
307                 // we also accept groups while reading which could instantiated but not could not be read
308                 result = groups.insert(fg, OFTrue);
309                 if (result.good())
310                 {
311                     DCMFG_DEBUG("Inserted functional group: " << fgname);
312                 }
313                 else
314                 {
315                     DCMFG_ERROR("Could not insert functional group: " << fgname << " (internal error)");
316                     delete fg;
317                 }
318             }
319             else
320             {
321                 DCMFG_WARN("Cannot understand functional group for sequence tag: " << elem->getTag());
322             }
323         }
324     }
325     return EC_Normal; // for now we always return EC_Normal...
326 }
327 
328 // Write enhanced multi-frame information to DICOM item, usually DcmDataset
write(DcmItem & dataset)329 OFCondition FGInterface::write(DcmItem& dataset)
330 {
331     // Check data integrity of functional group macros */
332     if (m_checkOnWrite)
333     {
334         if (!check())
335             return FG_EC_CouldNotWriteFG;
336     }
337 
338     // Write shared functional Groups
339     OFCondition result = writeSharedFG(dataset);
340 
341     // Write per frame functional groups
342     if (result.good())
343         result = writePerFrameFG(dataset);
344 
345     return result;
346 }
347 
getShared(const DcmFGTypes::E_FGType fgType)348 FGBase* FGInterface::getShared(const DcmFGTypes::E_FGType fgType)
349 {
350     return m_shared.find(fgType);
351 }
352 
insertShared(FGBase * group,const OFBool replaceExisting)353 OFCondition FGInterface::insertShared(FGBase* group, const OFBool replaceExisting)
354 {
355     return m_shared.insert(group, replaceExisting);
356 }
357 
getPerFrame(const Uint32 frameNo,const DcmFGTypes::E_FGType fgType)358 FGBase* FGInterface::getPerFrame(const Uint32 frameNo, const DcmFGTypes::E_FGType fgType)
359 {
360     FGBase* group                                 = NULL;
361     OFMap<Uint32, FunctionalGroups*>::iterator it = m_perFrame.find(frameNo);
362     if (it != m_perFrame.end())
363     {
364         FunctionalGroups* perFrameGroups = (*it).second;
365         group                            = perFrameGroups->find(fgType);
366     }
367 
368     return group;
369 }
370 
deleteShared(const DcmFGTypes::E_FGType fgType)371 OFBool FGInterface::deleteShared(const DcmFGTypes::E_FGType fgType)
372 {
373     FGBase* group = m_shared.find(fgType);
374     if (group)
375     {
376         delete m_shared.remove(fgType);
377         return OFTrue;
378     }
379     return OFFalse;
380 }
381 
deletePerFrame(const Uint32 frameNo,const DcmFGTypes::E_FGType fgType)382 OFBool FGInterface::deletePerFrame(const Uint32 frameNo, const DcmFGTypes::E_FGType fgType)
383 {
384     OFMap<Uint32, FunctionalGroups*>::iterator it = m_perFrame.find(frameNo);
385     if (it != m_perFrame.end())
386     {
387         if ((*it).second)
388         {
389             FGBase* remove = (*it).second->remove(fgType);
390             if (remove)
391             {
392                 DCMFG_DEBUG("Deleting FG for frame " << frameNo << ", type: " << DcmFGTypes::FGType2OFString(fgType));
393                 delete remove;
394                 remove = NULL;
395                 return OFTrue;
396             }
397         }
398     }
399     return OFFalse;
400 }
401 
deletePerFrame(const DcmFGTypes::E_FGType fgType)402 size_t FGInterface::deletePerFrame(const DcmFGTypes::E_FGType fgType)
403 {
404     size_t numDeleted      = 0;
405     const size_t numFrames = m_perFrame.size();
406     for (size_t frameNo = 0; frameNo < numFrames; frameNo++)
407     {
408         if (deletePerFrame(OFstatic_cast(Uint32, frameNo), fgType))
409         {
410             numDeleted++;
411         }
412     }
413     return numDeleted;
414 }
415 
deleteFrame(const Uint32 frameNo)416 size_t FGInterface::deleteFrame(const Uint32 frameNo)
417 {
418     OFMap<Uint32, FunctionalGroups*>::iterator it = m_perFrame.find(frameNo);
419     if (it != m_perFrame.end())
420     {
421         if ((*it).second)
422         {
423             FunctionalGroups::iterator fg = (*it).second->begin();
424             while (fg != (*it).second->end())
425             {
426                 delete (*fg).second;
427                 fg++;
428             }
429         }
430         m_perFrame.erase(it);
431     }
432     return OFFalse;
433 }
434 
setCheckOnWrite(const OFBool doCheck)435 void FGInterface::setCheckOnWrite(const OFBool doCheck)
436 {
437     m_checkOnWrite = doCheck;
438 }
439 
getCheckOnWrite()440 OFBool FGInterface::getCheckOnWrite()
441 {
442     return m_checkOnWrite;
443 }
444 
getOrCreatePerFrameGroups(const Uint32 frameNo)445 FunctionalGroups* FGInterface::getOrCreatePerFrameGroups(const Uint32 frameNo)
446 {
447     OFMap<Uint32, FunctionalGroups*>::iterator it = m_perFrame.find(frameNo);
448     if (it != m_perFrame.end())
449         return (*it).second;
450 
451     FunctionalGroups* fg = new FunctionalGroups();
452     if (fg != NULL)
453     {
454         if (!(m_perFrame.insert(OFMake_pair(frameNo, fg))).second)
455         {
456             DCMFG_ERROR("Could not insert Per-frame Functional Groups for frame " << frameNo << ": "
457                                                                                   << "Internal error");
458             delete fg;
459             fg = NULL;
460         }
461     }
462     else
463     {
464         DCMFG_ERROR("Could not create Per-frame Functional Groups for frame " << frameNo << ": "
465                                                                               << "Memory exhausted");
466     }
467 
468     return fg;
469 }
470 
writePerFrameFG(DcmItem & dataset)471 OFCondition FGInterface::writePerFrameFG(DcmItem& dataset)
472 {
473     DCMFG_DEBUG("Writing per-frame functional groups");
474     OFCondition result
475         = dataset.insertEmptyElement(DCM_PerFrameFunctionalGroupsSequence, OFTrue); // start with empty sequence
476     if (result.bad())
477     {
478         DCMFG_ERROR("Could not create Per-frame Functional Groups Sequence");
479         return result;
480     }
481 
482     /* Iterate over frames */
483     OFMap<Uint32, FunctionalGroups*>::iterator it = m_perFrame.begin();
484     size_t numFrames                              = m_perFrame.size();
485     for (size_t count = 0; (count < numFrames) && result.good(); count++)
486     {
487         DcmItem* perFrameItem = NULL;
488         result                = dataset.findOrCreateSequenceItem(
489             DCM_PerFrameFunctionalGroupsSequence, perFrameItem, OFstatic_cast(long, count));
490         if (result.good())
491         {
492             /* Iterate over groups for each frame */
493             FunctionalGroups::iterator groupIt = (*it).second->begin();
494             while (result.good() && (groupIt != (*it).second->end()))
495             {
496                 DCMFG_DEBUG("Writing per-frame group: " << DcmFGTypes::FGType2OFString((*groupIt).second->getType())
497                                                         << " for frame #" << count);
498                 result = (*groupIt).second->write(*perFrameItem);
499                 groupIt++;
500             }
501         }
502         else
503         {
504             DCMFG_ERROR("Cannot create item in Per-frame Functional Groups Sequence");
505         }
506         it++;
507     }
508     return result;
509 }
510 
writeSharedFG(DcmItem & dataset)511 OFCondition FGInterface::writeSharedFG(DcmItem& dataset)
512 {
513     DCMFG_DEBUG("Writing shared functional groups");
514     OFCondition result
515         = dataset.insertEmptyElement(DCM_SharedFunctionalGroupsSequence, OFTrue); // start with empty sequence
516     DcmItem* sharedFGItem = NULL;
517     if (result.good())
518     {
519         result = dataset.findOrCreateSequenceItem(DCM_SharedFunctionalGroupsSequence, sharedFGItem, 0);
520     }
521     if (result.bad())
522     {
523         DCMFG_ERROR("Could not create Shared Functional Groups Sequence with single item");
524         return result;
525     }
526 
527     FunctionalGroups::iterator it  = m_shared.begin();
528     FunctionalGroups::iterator end = m_shared.end();
529     while ((it != end) && result.good())
530     {
531         DCMFG_DEBUG("Writing shared group: " << DcmFGTypes::FGType2OFString((*it).second->getType()));
532         result = (*it).second->write(*sharedFGItem);
533         it++;
534     }
535     return result;
536 }
537 
insertPerFrame(const Uint32 frameNo,FGBase * group,const OFBool replaceExisting)538 OFCondition FGInterface::insertPerFrame(const Uint32 frameNo, FGBase* group, const OFBool replaceExisting)
539 {
540     if (group == NULL)
541         return EC_IllegalParameter;
542 
543     OFCondition result = EC_Normal;
544     FGBase* existing   = getPerFrame(frameNo, group->getType());
545     if (existing)
546     {
547         if (replaceExisting)
548         {
549             DCMFG_DEBUG("Replacing per-frame FG for frame: " << frameNo << ", type: "
550                                                              << DcmFGTypes::FGType2OFString(group->getType()));
551             deletePerFrame(frameNo, group->getType());
552         }
553         else
554         {
555             result = FG_EC_DoubledFG;
556         }
557     }
558 
559     // Insert per-frame functional group
560     if (result.good())
561     {
562         FunctionalGroups* perFrameGroups = getOrCreatePerFrameGroups(frameNo);
563         if (perFrameGroups != NULL)
564         {
565             result = perFrameGroups->insert(group, replaceExisting);
566         }
567         else
568         {
569             result = FG_EC_CouldNotInsertFG;
570         }
571     }
572     return result;
573 }
574 
convertSharedToPerFrame(const DcmFGTypes::E_FGType fgType)575 OFCondition FGInterface::convertSharedToPerFrame(const DcmFGTypes::E_FGType fgType)
576 {
577     FGBase* shared = m_shared.remove(fgType);
578     if (!shared)
579     {
580         return FG_EC_NoSuchGroup;
581     }
582 
583     OFCondition result;
584     size_t numFrames = m_perFrame.size();
585     // Walk over all existing frames and copy "old" shared group to them
586     size_t count = 0;
587     for (count = 0; result.good() && (count < numFrames); count++)
588     {
589         FGBase* clone = shared->clone();
590         if (!clone)
591         {
592             result = EC_MemoryExhausted;
593         }
594         else
595         {
596             result = insertPerFrame(OFstatic_cast(Uint32, count), clone, OFTrue /* replace existing */);
597             if (result.bad())
598             {
599                 delete clone;
600             }
601         }
602     }
603     return result;
604 }
605 
get(const Uint32 frameNo,const DcmFGTypes::E_FGType fgType,OFBool & isPerFrame)606 FGBase* FGInterface::get(const Uint32 frameNo, const DcmFGTypes::E_FGType fgType, OFBool& isPerFrame)
607 {
608     FGBase* group = m_shared.find(fgType);
609     if (!group)
610     {
611         group      = getPerFrame(frameNo, fgType);
612         isPerFrame = OFTrue;
613     }
614     else
615     {
616         isPerFrame = OFFalse;
617     }
618 
619     return group;
620 }
621 
check()622 OFBool FGInterface::check()
623 {
624     size_t numFrames = m_perFrame.size();
625     DCMFG_DEBUG("Checking functional group structure for " << numFrames << " frames");
626     size_t numErrors = 0;
627     for (size_t frameCount = 0; frameCount < numFrames; frameCount++)
628     {
629         DCMFG_TRACE("Checking frame " << frameCount << "...");
630         // Every frame requires the FrameContent functional group, check "en passant"
631         OFBool foundFrameContent                           = OFFalse;
632         OFMap<Uint32, FunctionalGroups*>::iterator frameFG = m_perFrame.begin();
633         OFMap<Uint32, FunctionalGroups*>::iterator end     = m_perFrame.end();
634         while (frameFG != end)
635         {
636             FunctionalGroups::iterator group    = (*frameFG).second->begin();
637             FunctionalGroups::iterator groupEnd = (*frameFG).second->end();
638             while (group != groupEnd)
639             {
640                 // Check that per-frame group is not a shared group at the same time
641                 DcmFGTypes::E_FGType groupType = group->second->getType();
642                 if ((groupType != DcmFGTypes::EFG_UNDEFINED) && (groupType != DcmFGTypes::EFG_UNKNOWN))
643                 {
644                     if (m_shared.find(groupType) != NULL)
645                     {
646                         DCMFG_ERROR("Functional group of type " << DcmFGTypes::FGType2OFString(groupType)
647                                                                 << " is shared AND per-frame for frame " << frameCount);
648                         numErrors++;
649                     }
650                     if (groupType == DcmFGTypes::EFG_FRAMECONTENT)
651                         foundFrameContent = OFTrue;
652                 }
653                 // Check if "per-frame" is allowed for this group;
654                 if (group->second->getSharedType() == DcmFGTypes::EFGS_ONLYSHARED)
655                 {
656                     DCMFG_ERROR("Functional group of type " << DcmFGTypes::FGType2OFString(groupType)
657                                                             << " can never be per-frame, but found for frame "
658                                                             << frameCount);
659                     numErrors++;
660                 }
661                 group++;
662             }
663             frameFG++;
664         }
665         if (!foundFrameContent)
666         {
667             DCMFG_ERROR("Frame Content Functional group missing for frame #" << frameCount);
668             numErrors++;
669         }
670     }
671 
672     // Check whether shared groups contain FGs that are only permitted per-frame
673     FunctionalGroups::iterator it  = m_shared.begin();
674     FunctionalGroups::iterator end = m_shared.end();
675     while (it != end)
676     {
677         if ((*it).second->getSharedType() == DcmFGTypes::EFGS_ONLYPERFRAME)
678         {
679             DCMFG_ERROR("Functional group of type " << DcmFGTypes::FGType2OFString((*it).second->getType())
680                                                     << " used as shared functional group but must be per-frame");
681             numErrors++;
682         }
683         it++;
684     }
685 
686     if (numErrors > 0)
687         return OFFalse;
688 
689     return OFTrue;
690 }
691